您好,登錄后才能下訂單哦!
通過(guò)筆者前兩篇文章的說(shuō)明,相信大家已經(jīng)知道JWT是什么,怎么用,該如何結(jié)合Spring Security使用。那么本節(jié)就用代碼來(lái)具體的實(shí)現(xiàn)一下JWT登錄認(rèn)證及鑒權(quán)的流程。
一、環(huán)境準(zhǔn)備工作
csrf().disable()
,即暫時(shí)關(guān)掉跨站攻擊CSRF的防御。這樣是不安全的,我們后續(xù)章節(jié)再做處理。以上的內(nèi)容,我們?cè)谥暗奈恼轮卸家呀?jīng)講過(guò)。如果仍然不熟悉,可以翻看本號(hào)之前的文章。
## 二、開發(fā)JWT工具類
通過(guò)maven坐標(biāo)引入JWT工具包jjwt
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
在application.yml中加入如下自定義一些關(guān)于JWT的配置
jwt: header: JWTHeaderName secret: aabbccdd expiration: 3600000
寫一個(gè)Spring Boot配置自動(dòng)加載的工具類。
@Data @ConfigurationProperties(prefix = "jwt") //配置自動(dòng)加載,prefix是配置的前綴 @Component public class JwtTokenUtil implements Serializable { private String secret; private Long expiration; private String header; /** * 生成token令牌 * * @param userDetails 用戶 * @return 令token牌 */ public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(2); claims.put("sub", userDetails.getUsername()); claims.put("created", new Date()); return generateToken(claims); } /** * 從令牌中獲取用戶名 * * @param token 令牌 * @return 用戶名 */ public String getUsernameFromToken(String token) { String username; try { Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) { username = null; } return username; } /** * 判斷令牌是否過(guò)期 * * @param token 令牌 * @return 是否過(guò)期 */ public Boolean isTokenExpired(String token) { try { Claims claims = getClaimsFromToken(token); Date expiration = claims.getExpiration(); return expiration.before(new Date()); } catch (Exception e) { return false; } } /** * 刷新令牌 * * @param token 原令牌 * @return 新令牌 */ public 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; } /** * 驗(yàn)證令牌 * * @param token 令牌 * @param userDetails 用戶 * @return 是否有效 */ public Boolean validateToken(String token, UserDetails userDetails) { SysUser user = (SysUser) userDetails; String username = getUsernameFromToken(token); return (username.equals(user.getUsername()) && !isTokenExpired(token)); } /** * 從claims生成令牌,如果看不懂就看誰(shuí)調(diào)用它 * * @param claims 數(shù)據(jù)聲明 * @return 令牌 */ private String generateToken(Map<String, Object> claims) { Date expirationDate = new Date(System.currentTimeMillis() + expiration); return Jwts.builder().setClaims(claims) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } /** * 從令牌中獲取數(shù)據(jù)聲明,如果看不懂就看誰(shuí)調(diào)用它 * * @param token 令牌 * @return 數(shù)據(jù)聲明 */ private Claims getClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } catch (Exception e) { claims = null; } return claims; } }
上面的代碼就是使用io.jsonwebtoken.jjwt提供的方法開發(fā)JWT令牌生成、刷新的工具類。
三、開發(fā)登錄接口(獲取Token的接口)
@RESTCONTROLLER PUBLIC CLASS JWTAUTHCONTROLLER { @RESOURCE PRIVATE JWTAUTHSERVICE JWTAUTHSERVICE; @POSTMAPPING(VALUE = "/AUTHENTICATION") PUBLIC AJAXRESPONSE LOGIN(@REQUESTBODY MAP<STRING, STRING> MAP) { STRING USERNAME = MAP.GET("USERNAME"); STRING PASSWORD = MAP.GET("PASSWORD"); IF (STRINGUTILS.ISEMPTY(USERNAME) || STRINGUTILS.ISEMPTY(PASSWORD)) { RETURN AJAXRESPONSE.ERROR( NEW CUSTOMEXCEPTION(CUSTOMEXCEPTIONTYPE.USER_INPUT_ERROR,"用戶名密碼不能為空")); } RETURN AJAXRESPONSE.SUCCESS(JWTAUTHSERVICE.LOGIN(USERNAME, PASSWORD)); } @POSTMAPPING(VALUE = "/REFRESHTOKEN") PUBLIC AJAXRESPONSE REFRESH(@REQUESTHEADER("${JWT.HEADER}") STRING TOKEN) { RETURN AJAXRESPONSE.SUCCESS(JWTAUTHSERVICE.REFRESHTOKEN(TOKEN)); } }
核心的token業(yè)務(wù)邏輯寫在JwtAuthService 中
@Service public class JwtAuthService { @Resource private AuthenticationManager authenticationManager; @Resource private UserDetailsService userDetailsService; @Resource private JwtTokenUtil jwtTokenUtil; public String login(String username, String password) { //使用用戶名密碼進(jìn)行登錄驗(yàn)證 UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password ); Authentication authentication = authenticationManager.authenticate(upToken); SecurityContextHolder.getContext().setAuthentication(authentication); //生成JWT UserDetails userDetails = userDetailsService.loadUserByUsername( username ); return jwtTokenUtil.generateToken(userDetails); } public String refreshToken(String oldToken) { if (!jwtTokenUtil.isTokenExpired(oldToken)) { return jwtTokenUtil.refreshToken(oldToken); } return null; } }
因?yàn)槭褂玫搅薃uthenticationManager ,所以在繼承WebSecurityConfigurerAdapter的SpringSecurity配置實(shí)現(xiàn)類中,將AuthenticationManager 聲明為一個(gè)Bean。并將"/authentication"和 "/refreshtoken"開放訪問(wèn)權(quán)限,如何開放訪問(wèn)權(quán)限,我們之前的文章已經(jīng)講過(guò)了。
@Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
四、接口訪問(wèn)鑒權(quán)過(guò)濾器
當(dāng)用戶第一次登陸之后,我們將JWT令牌返回給了客戶端,客戶端應(yīng)該將該令牌保存起來(lái)。在進(jìn)行接口請(qǐng)求的時(shí)候,將令牌帶上,放到HTTP的header里面,header的名字要和jwt.header的配置一致,這樣服務(wù)端才能解析到。下面我們定義一個(gè)攔截器:
@Slf4j @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Resource private MyUserDetailsService userDetailsService; @Resource private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 從這里開始獲取 request 中的 jwt token String authHeader = request.getHeader(jwtTokenUtil.getHeader()); log.info("authHeader:{}", authHeader); // 驗(yàn)證token是否存在 if (authHeader != null && StringUtils.isNotEmpty(authHeader)) { // 根據(jù)token 獲取用戶名 String username = jwtTokenUtil.getUsernameFromToken(authHeader); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { // 通過(guò)用戶名 獲取用戶的信息 UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); // 驗(yàn)證JWT是否過(guò)期 if (jwtTokenUtil.validateToken(authHeader, userDetails)) { //加載用戶、角色、權(quán)限信息,Spring Security根據(jù)這些信息判斷接口的訪問(wèn)權(quán)限 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource() .buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); } }
在spring Security的配置類(即WebSecurityConfigurerAdapter實(shí)現(xiàn)類的configure(HttpSecurity http)配置方法中,加入如下配置:
.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
五、測(cè)試一下:
測(cè)試登錄接口,即:獲取token的接口。輸入正確的用戶名、密碼即可獲取token。
下面我們?cè)L問(wèn)一個(gè)我們定義的簡(jiǎn)單的接口“/hello”,但是不傳遞JWT令牌,結(jié)果是禁止訪問(wèn)。當(dāng)我們將上一步返回的token,傳遞到header中,就能正常響應(yīng)hello的接口結(jié)果。
總結(jié)
以上所述是小編給大家介紹的Spring Security代碼實(shí)現(xiàn)JWT接口權(quán)限授予與校驗(yàn)功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)億速云網(wǎng)站的支持!
如果你覺(jué)得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。