15.Spring Security結(jié)果JWT實(shí)現(xiàn)用戶認(rèn)證

1.demo-admin模塊引入依賴

        <!-- spring security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- jwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

2.編寫Spring Security認(rèn)證配置類

package com.lvxk.demo.admin.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;


/**
 * WebSecurityConfig
 * Description: <br/>
 * date: 2020/5/5 15:36<br/>
 *
 * @author lvxk<br />
 * @since JDK 1.8
 */
@Configuration
@EnableWebSecurity  // 開啟Spring Security
@EnableGlobalMethodSecurity(prePostEnabled = true)  // 開啟權(quán)限注解,如:@PreAuthorize注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 使用自定義身份驗(yàn)證組件
        auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 禁用 csrf, 由于使用的是JWT肺孵,我們這里不需要csrf
        http.cors().and().csrf().disable()
                .authorizeRequests()
                // 跨域預(yù)檢請(qǐng)求
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                // web jars
                .antMatchers("/webjars/**").permitAll()
                // 查看SQL監(jiān)控(druid)
                .antMatchers("/druid/**").permitAll()
                // 首頁和登錄頁面
                .antMatchers("/").permitAll()
                .antMatchers("/login").permitAll()
                // swagger
                .antMatchers("/swagger-ui.html").permitAll()
                .antMatchers("/swagger-resources/**").permitAll()
                .antMatchers("/v2/api-docs").permitAll()
                .antMatchers("/webjars/springfox-swagger-ui/**").permitAll()
                // 驗(yàn)證碼
                .antMatchers("/captcha/**").permitAll()
                // 服務(wù)監(jiān)控
                .antMatchers("/actuator/**").permitAll()
                // 其他所有請(qǐng)求需要身份認(rèn)證
                .anyRequest().authenticated();
        // 退出登錄處理器
        http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
        // token驗(yàn)證過濾器
        http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
}

3.新建security包,編寫登錄認(rèn)證過濾器

package com.lvxk.demo.admin.security;
import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;


/**
 * JwtAuthenticationFilter
 * Description: <br/>
 * date: 2020/5/5 15:39<br/>
 *
 * @author lvxk<br />
 * @since JDK 1.8
 */
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {


    @Autowired
    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 獲取token, 并檢查登錄狀態(tài)
        SecurityUtils.checkAuthentication(request);
        chain.doFilter(request, response);
    }

}

4.創(chuàng)建util包和SecurityUtils

package com.lvxk.demo.admin.util;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;

/**
 * SecurityUtils
 * Description: <br/>
 * date: 2020/5/5 15:42<br/>
 *
 * @author lvxk<br />
 * @since JDK 1.8
 */
public class SecurityUtils {
    /**
     * 系統(tǒng)登錄認(rèn)證
     * @param request
     * @param username
     * @param password
     * @param authenticationManager
     * @return
     */
    public static JwtAuthenticatioToken login(HttpServletRequest request, String username, String password, AuthenticationManager authenticationManager) {
        JwtAuthenticatioToken token = new JwtAuthenticatioToken(username, password);
        token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        // 執(zhí)行登錄認(rèn)證過程
        Authentication authentication = authenticationManager.authenticate(token);
        // 認(rèn)證成功存儲(chǔ)認(rèn)證信息到上下文
        SecurityContextHolder.getContext().setAuthentication(authentication);
        // 生成令牌并返回給客戶端
        token.setToken(JwtTokenUtils.generateToken(authentication));
        return token;
    }

    /**
     * 獲取令牌進(jìn)行認(rèn)證
     * @param request
     */
    public static void checkAuthentication(HttpServletRequest request) {
        // 獲取令牌并根據(jù)令牌獲取登錄認(rèn)證信息
        Authentication authentication = JwtTokenUtils.getAuthenticationeFromToken(request);
        // 設(shè)置登錄認(rèn)證信息到上下文
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    /**
     * 獲取當(dāng)前用戶名
     * @return
     */
    public static String getUsername() {
        String username = null;
        Authentication authentication = getAuthentication();
        if(authentication != null) {
            Object principal = authentication.getPrincipal();
            if(principal != null && principal instanceof UserDetails) {
                username = ((UserDetails) principal).getUsername();
            }
        }
        return username;
    }

    /**
     * 獲取用戶名
     * @return
     */
    public static String getUsername(Authentication authentication) {
        String username = null;
        if(authentication != null) {
            Object principal = authentication.getPrincipal();
            if(principal != null && principal instanceof UserDetails) {
                username = ((UserDetails) principal).getUsername();
            }
        }
        return username;
    }

    /**
     * 獲取當(dāng)前登錄信息
     * @return
     */
    public static Authentication getAuthentication() {
        if(SecurityContextHolder.getContext() == null) {
            return null;
        }
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return authentication;
    }

}

5.JwtTokenUtils

package com.lvxk.demo.admin.util;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

/**
 * JwtTokenUtils
 * Description: <br/>
 * date: 2020/5/5 15:45<br/>
 *
 * @author lvxk<br />
 * @since JDK 1.8
 */
public class JwtTokenUtils implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 用戶名稱
     */
    private static final String USERNAME = Claims.SUBJECT;
    /**
     * 創(chuàng)建時(shí)間
     */
    private static final String CREATED = "created";
    /**
     * 權(quán)限列表
     */
    private static final String AUTHORITIES = "authorities";
    /**
     * 密鑰
     */
    private static final String SECRET = "abcdefgh";
    /**
     * 有效期12小時(shí)
     */
    private static final long EXPIRE_TIME = 12 * 60 * 60 * 1000;

    /**
     * 生成令牌
     *
     * @param authentication 用戶
     * @return 令牌
     */
    public static String generateToken(Authentication authentication) {
        Map<String, Object> claims = new HashMap<>(3);
        claims.put(USERNAME, SecurityUtils.getUsername(authentication));
        claims.put(CREATED, new Date());
        claims.put(AUTHORITIES, authentication.getAuthorities());
        return generateToken(claims);
    }

    /**
     * 從數(shù)據(jù)聲明生成令牌
     *
     * @param claims 數(shù)據(jù)聲明
     * @return 令牌
     */
    private static String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, SECRET).compact();
    }

    /**
     * 從令牌中獲取用戶名
     *
     * @param token 令牌
     * @return 用戶名
     */
    public static String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 根據(jù)請(qǐng)求令牌獲取登錄認(rèn)證信息
     * @param request 令牌
     * @return 用戶名
     */
    public static Authentication getAuthenticationeFromToken(HttpServletRequest request) {
        Authentication authentication = null;
        // 獲取請(qǐng)求攜帶的令牌
        String token = JwtTokenUtils.getToken(request);
        if(token != null) {
            // 請(qǐng)求令牌不能為空
            if(SecurityUtils.getAuthentication() == null) {
                // 上下文中Authentication為空
                Claims claims = getClaimsFromToken(token);
                if(claims == null) {
                    return null;
                }
                String username = claims.getSubject();
                if(username == null) {
                    return null;
                }
                if(isTokenExpired(token)) {
                    return null;
                }
                Object authors = claims.get(AUTHORITIES);
                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
                if (authors != null && authors instanceof List) {
                    for (Object object : (List) authors) {
                        authorities.add(new GrantedAuthorityImpl((String) ((Map) object).get("authority")));
                    }
                }
                authentication = new JwtAuthenticatioToken(username, null, authorities, token);
            } else {
                if(validateToken(token, SecurityUtils.getUsername())) {
                    // 如果上下文中Authentication非空,且請(qǐng)求令牌合法厨喂,直接返回當(dāng)前登錄認(rèn)證信息
                    authentication = SecurityUtils.getAuthentication();
                }
            }
        }
        return authentication;
    }

    /**
     * 從令牌中獲取數(shù)據(jù)聲明
     *
     * @param token 令牌
     * @return 數(shù)據(jù)聲明
     */
    private static Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 驗(yàn)證令牌
     * @param token
     * @param username
     * @return
     */
    public static Boolean validateToken(String token, String username) {
        String userName = getUsernameFromToken(token);
        return (userName.equals(username) && !isTokenExpired(token));
    }

    /**
     * 刷新令牌
     * @param token
     * @return
     */
    public static String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            claims.put(CREATED, new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 判斷令牌是否過期
     *
     * @param token 令牌
     * @return 是否過期
     */
    public static Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 獲取請(qǐng)求token
     * @param request
     * @return
     */
    public static String getToken(HttpServletRequest request) {
        String token = request.getHeader("Authorization");
        String tokenHead = "Bearer ";
        if(token == null) {
            token = request.getHeader("token");
        } else if(token.contains(tokenHead)){
            token = token.substring(tokenHead.length());
        }
        if("".equals(token)) {
            token = null;
        }
        return token;
    }

}

6.JwtAuthenticationProvider

package com.lvxk.demo.admin.security;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

/**
 * JwtAuthenticationProvider 
 * Description: <br/>
 * date: 2020/5/5 15:57<br/>
 *
 * @author lvxk<br />
 * @since JDK 1.8
 */
public class JwtAuthenticationProvider extends DaoAuthenticationProvider {

    public JwtAuthenticationProvider(UserDetailsService userDetailsService) {
        setUserDetailsService(userDetailsService);
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }

        String presentedPassword = authentication.getCredentials().toString();
        String salt = ((JwtUserDetails) userDetails).getSalt();
        // 覆寫密碼驗(yàn)證邏輯
        if (!new PasswordEncoder(salt).matches(userDetails.getPassword(), presentedPassword)) {
            logger.debug("Authentication failed: password does not match stored value");
            throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }

}

7.認(rèn)證信息查詢 security包下新建一個(gè)UserDetailsServiceImpl

package com.lvxk.demo.admin.security;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * 用戶登錄認(rèn)證信息查詢
 * Description: <br/>
 * date: 2020/5/5 15:59<br/>
 *
 * @author lvxk<br />
 * @since JDK 1.8
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private SysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = sysUserService.findByName(username);
        if (user == null) {
            throw new UsernameNotFoundException("該用戶不存在");
        }
        // 用戶權(quán)限列表赶熟,根據(jù)用戶擁有的權(quán)限標(biāo)識(shí)與如 @PreAuthorize("hasAuthority('sys:menu:view')") 標(biāo)注的接口對(duì)比,決定是否可以調(diào)用接口
        Set<String> permissions = sysUserService.findPermissions(user.getName());
        List<GrantedAuthority> grantedAuthorities = permissions.stream().map(GrantedAuthorityImpl::new).collect(Collectors.toList());
        return new JwtUserDetails(user.getName(), user.getPassword(), user.getSalt(), grantedAuthorities);
    }
}

8.安全用戶模型JwtUserDetails

package com.lvxk.demo.admin.security;
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.fasterxml.jackson.annotation.JsonIgnore;

/**
 * 安全用戶模型
 * Description: <br/>
 * date: 2020/5/5 16:05<br/>
 *
 * @author lvxk<br />
 * @since JDK 1.8
 */
public class JwtUserDetails implements UserDetails {

    private static final long serialVersionUID = 1L;

    private String username;
    private String password;
    private String salt;
    private Collection<? extends GrantedAuthority> authorities;

    JwtUserDetails(String username, String password, String salt, Collection<? extends GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.salt = salt;
        this.authorities = authorities;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @JsonIgnore
    @Override
    public String getPassword() {
        return password;
    }

    public String getSalt() {
        return salt;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return true;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市杯巨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌努酸,老刑警劉巖服爷,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚊逢,居然都是意外死亡层扶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門烙荷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镜会,“玉大人,你說我怎么就攤上這事终抽〈帘恚” “怎么了桶至?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長匾旭。 經(jīng)常有香客問我镣屹,道長,這世上最難降的妖魔是什么价涝? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任女蜈,我火速辦了婚禮,結(jié)果婚禮上色瘩,老公的妹妹穿的比我還像新娘伪窖。我一直安慰自己,他們只是感情好居兆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布覆山。 她就那樣靜靜地躺著,像睡著了一般泥栖。 火紅的嫁衣襯著肌膚如雪簇宽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天吧享,我揣著相機(jī)與錄音魏割,去河邊找鬼。 笑死耙蔑,一個(gè)胖子當(dāng)著我的面吹牛见妒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播甸陌,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼须揣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钱豁?” 一聲冷哼從身側(cè)響起耻卡,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎牲尺,沒想到半個(gè)月后卵酪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谤碳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年溃卡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜒简。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瘸羡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搓茬,到底是詐尸還是另有隱情犹赖,我是刑警寧澤队他,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站峻村,受9級(jí)特大地震影響麸折,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜粘昨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一垢啼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雾棺,春花似錦膊夹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽工秩。三九已至尸饺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間助币,已是汗流浹背浪听。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留眉菱,地道東北人迹栓。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像俭缓,于是被迫代替她去往敵國和親克伊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353