SpringBoot?SpringSecurity?JWT實(shí)現(xiàn)系統(tǒng)安全策略詳解
security進(jìn)行用戶驗(yàn)證和授權(quán);jwt負(fù)責(zé)頒發(fā)令牌與校驗(yàn),判斷用戶登錄狀態(tài)
一、原理
1. SpringSecurity過(guò)濾器鏈

SpringSecurity 采用的是責(zé)任鏈的設(shè)計(jì)模式,它有一條很長(zhǎng)的過(guò)濾器鏈。
- SecurityContextPersistenceFilter:在每次請(qǐng)求處理之前將該請(qǐng)求相關(guān)的安全上下文信息加載到 SecurityContextHolder 中。
- LogoutFilter:用于處理退出登錄。
- UsernamePasswordAuthenticationFilter:用于處理基于表單的登錄請(qǐng)求,從表單中獲取用戶名和密碼。
- BasicAuthenticationFilter:檢測(cè)和處理 http basic 認(rèn)證。
- ExceptionTranslationFilter:處理 AccessDeniedException 和 AuthenticationException 異常。
- FilterSecurityInterceptor:可以看做過(guò)濾器鏈的出口。
- …
流程說(shuō)明:客戶端發(fā)起一個(gè)請(qǐng)求,進(jìn)入 Security 過(guò)濾器鏈。
當(dāng)?shù)?LogoutFilter 的時(shí)候判斷是否是登出路徑,如果是登出路徑則到 logoutHandler ,如果登出成功則到 logoutSuccessHandler 登出成功處理,如果登出失敗則由 ExceptionTranslationFilter ;如果不是登出路徑則直接進(jìn)入下一個(gè)過(guò)濾器。
當(dāng)?shù)?UsernamePasswordAuthenticationFilter 的時(shí)候判斷是否為登錄路徑,如果是,則進(jìn)入該過(guò)濾器進(jìn)行登錄操作,如果登錄失敗則到 AuthenticationFailureHandler 登錄失敗處理器處理,如果登錄成功則到 AuthenticationSuccessHandler 登錄成功處理器處理,如果不是登錄請(qǐng)求則不進(jìn)入該過(guò)濾器。
當(dāng)?shù)?FilterSecurityInterceptor 的時(shí)候會(huì)拿到 uri ,根據(jù) uri 去找對(duì)應(yīng)的鑒權(quán)管理器,鑒權(quán)管理器做鑒權(quán)工作,鑒權(quán)成功則到 Controller 層否則到 AccessDeniedHandler 鑒權(quán)失敗處理器處理。
2. JWT校驗(yàn)

首先前端一樣是把登錄信息發(fā)送給后端,后端查詢數(shù)據(jù)庫(kù)校驗(yàn)用戶的賬號(hào)和密碼是否正確,正確的話則使用jwt生成token,并且返回給前端。以后前端每次請(qǐng)求時(shí),都需要攜帶token,后端獲取token后,使用jwt進(jìn)行驗(yàn)證用戶的token是否無(wú)效或過(guò)期,驗(yàn)證成功后才去做相應(yīng)的邏輯。
二、Security+JWT配置說(shuō)明
1. 添加maven依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>2. securityConfig配置
/**
* Security 配置
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
LoginFailureHandler loginFailureHandler;
@Autowired
LoginSuccessHandler loginSuccessHandler;
@Autowired
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
JwtAccessDeniedHandler jwtAccessDeniedHandler;
@Autowired
UserDetailServiceImpl userDetailService;
@Autowired
JWTLogoutSuccessHandler jwtLogoutSuccessHandler;
@Autowired
CaptchaFilter captchaFilter;
@Value("${security.enable}")
private Boolean securityIs = Boolean.TRUE;
@Value("${security.permit}")
private String permit;
@Bean
public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
//此處可添加別的規(guī)則,目前只設(shè)置 允許雙 //
firewall.setAllowUrlEncodedDoubleSlash(true);
return firewall;
}
@Bean
JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager(), jwtAuthenticationEntryPoint);
return jwtAuthenticationFilter;
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.cors().and().csrf().disable()
// 登錄配置
.formLogin()
.successHandler(loginSuccessHandler)
.failureHandler(loginFailureHandler)
.and()
.logout()
.logoutSuccessHandler(jwtLogoutSuccessHandler)
// 禁用session
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 配置攔截規(guī)則
.and()
.authorizeRequests()
.antMatchers(permit.split(",")).permitAll();
if (!securityIs) {
http.authorizeRequests().antMatchers("/**").permitAll();
}
registry.anyRequest().authenticated()
// 異常處理器
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 配置自定義的過(guò)濾器
.and()
.addFilter(jwtAuthenticationFilter())
// 驗(yàn)證碼過(guò)濾器放在UsernamePassword過(guò)濾器之前
.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService);
}
}3. JwtAuthenticationFilter校驗(yàn)token
package cn.piesat.gf.filter;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.piesat.gf.dao.user.SysUserDao;
import cn.piesat.gf.model.entity.user.SysUser;
import cn.piesat.gf.exception.ExpiredAuthenticationException;
import cn.piesat.gf.exception.MyAuthenticationException;
import cn.piesat.gf.service.user.impl.UserDetailServiceImpl;
import cn.piesat.gf.utils.Constants;
import cn.piesat.gf.utils.JwtUtils;
import cn.piesat.gf.utils.Result;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager;
@Autowired
JwtUtils jwtUtils;
@Autowired
UserDetailServiceImpl userDetailService;
@Autowired
SysUserDao sysUserRepository;
@Autowired
RedisTemplate redisTemplate;
@Value("${security.single}")
private Boolean singleLogin = false;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
super(authenticationManager, authenticationEntryPoint);
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null");
this.authenticationManager = authenticationManager;
this.authenticationEntryPoint = authenticationEntryPoint;
}
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String jwt = request.getHeader(jwtUtils.getHeader());
// 這里如果沒(méi)有jwt,繼續(xù)往后走,因?yàn)楹竺孢€有鑒權(quán)管理器等去判斷是否擁有身份憑證,所以是可以放行的
// 沒(méi)有jwt相當(dāng)于匿名訪問(wèn),若有一些接口是需要權(quán)限的,則不能訪問(wèn)這些接口
if (StrUtil.isBlankOrUndefined(jwt)) {
chain.doFilter(request, response);
return;
}
try {
Claims claim = jwtUtils.getClaimsByToken(jwt);
if (claim == null) {
throw new MyAuthenticationException("token 異常");
}
if (jwtUtils.isTokenExpired(claim)) {
throw new MyAuthenticationException("token 已過(guò)期");
}
String username = claim.getSubject();
Object o1 = redisTemplate.opsForValue().get(Constants.TOKEN_KEY + username);
String o = null;
if(!ObjectUtils.isEmpty(o1)){
o = o1.toString();
}
if (!StringUtils.hasText(o)) {
throw new ExpiredAuthenticationException("您的登錄信息已過(guò)期,請(qǐng)重新登錄!");
}
if (singleLogin && StringUtils.hasText(o) && !jwt.equals(o)) {
throw new MyAuthenticationException("您的賬號(hào)已別處登錄,您已下線,如有異常請(qǐng)及時(shí)修改密碼!");
}
// 獲取用戶的權(quán)限等信息
SysUser sysUser = sysUserRepository.findByUserName(username);
// 構(gòu)建UsernamePasswordAuthenticationToken,這里密碼為null,是因?yàn)樘峁┝苏_的JWT,實(shí)現(xiàn)自動(dòng)登錄
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, userDetailService.getUserAuthority(sysUser.getUserId()));
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(request, response);
} catch (AuthenticationException e) {
log.error(ExceptionUtil.stacktraceToString(e));
authenticationEntryPoint.commence(request, response, e);
return;
} catch (Exception e){
log.error(ExceptionUtil.stacktraceToString(e));
response.getOutputStream().write(JSONUtil.toJsonStr(Result.fail(e.getMessage())).getBytes(StandardCharsets.UTF_8));
response.getOutputStream().flush();
response.getOutputStream().close();
return;
}
}
}4. JWT生成與解析工具類
package cn.piesat.gf.utils;
import cn.hutool.core.exceptions.ExceptionUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
@Data
@Component
@ConfigurationProperties(prefix = "jwt.config")
@Slf4j
public class JwtUtils {
private long expire;
private String secret;
private String header;
// 生成JWT
public String generateToken(String username) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + 1000 * expire);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(username)
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
// 解析JWT
public Claims getClaimsByToken(String jwt) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(jwt)
.getBody();
} catch (Exception e) {
log.error(ExceptionUtil.stacktraceToString(e));
return null;
}
}
// 判斷JWT是否過(guò)期
public boolean isTokenExpired(Claims claims) {
return claims.getExpiration().before(new Date());
}
}到此這篇關(guān)于SpringBoot SpringSecurity JWT實(shí)現(xiàn)系統(tǒng)安全策略詳解的文章就介紹到這了,更多相關(guān)SpringBoot SpringSecurity JWT內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
- SpringBoot+SpringSecurity+JWT實(shí)現(xiàn)系統(tǒng)認(rèn)證與授權(quán)示例
- SpringBoot+SpringSecurity+jwt實(shí)現(xiàn)驗(yàn)證
- SpringBoot整合SpringSecurity實(shí)現(xiàn)JWT認(rèn)證的項(xiàng)目實(shí)踐
- springboot+jwt+springSecurity微信小程序授權(quán)登錄問(wèn)題
- SpringBoot整合SpringSecurity和JWT的示例
- SpringBoot集成SpringSecurity和JWT做登陸鑒權(quán)的實(shí)現(xiàn)
- 詳解SpringBoot+SpringSecurity+jwt整合及初體驗(yàn)
- SpringBoot3.0+SpringSecurity6.0+JWT的實(shí)現(xiàn)
相關(guān)文章
spring-boot實(shí)現(xiàn)增加自定義filter(新)
本篇文章主要介紹了spring-boot實(shí)現(xiàn)增加自定義filter(新),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05
Spring Boot啟動(dòng)及退出加載項(xiàng)的方法
這篇文章主要介紹了Spring Boot啟動(dòng)及退出加載項(xiàng)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
IDEA中application.properties的圖標(biāo)顯示不正常的問(wèn)題及解決方法
這篇文章主要介紹了IDEA中application.properties的圖標(biāo)顯示不正常的問(wèn)題及解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
如何用ObjectMapper將復(fù)雜Map轉(zhuǎn)換為實(shí)體類
這篇文章主要介紹了如何用ObjectMapper將復(fù)雜Map轉(zhuǎn)換為實(shí)體類的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
使用SpringBoot Actuator監(jiān)控應(yīng)用示例
Actuator是Spring Boot提供的對(duì)應(yīng)用系統(tǒng)的自省和監(jiān)控的集成功能,可以對(duì)應(yīng)用系統(tǒng)進(jìn)行配置查看、相關(guān)功能統(tǒng)計(jì)等。這篇文章主要介紹了使用SpringBoot Actuator監(jiān)控應(yīng),有興趣的可以了解一下2018-05-05
java算法題解??虰M99順時(shí)針旋轉(zhuǎn)矩陣示例
這篇文章主要為大家介紹了java算法題解??虰M99順時(shí)針旋轉(zhuǎn)矩陣示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
java控制臺(tái)實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)(集合版)
這篇文章主要為大家詳細(xì)介紹了java控制臺(tái)實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)的集合版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04
SpringCloud實(shí)戰(zhàn)小貼士之Zuul的路徑匹配
這篇文章主要介紹了SpringCloud實(shí)戰(zhàn)小貼士之Zuul的路徑匹配,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10

