github源碼:
https://github.com/gyb123456/spring-security5-jwt,最煩那些寫(xiě)文檔只截圖一半還不給源碼的人,要不你就截全圖,要不就給源碼!
前言:
需要研究spring-security和jwt技術(shù),搞了幾天,現(xiàn)把成果記錄下
環(huán)境
Springboot:2.1.3.RELEASE
spring-security:5.1.4.RELEASE
前后端分離
所需依賴(lài)3個(gè),具體pom見(jiàn)源碼
<!--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.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
spring-security原理
從網(wǎng)上的各種不同方法綜合一下形成了這一套完整框架,源碼里每個(gè)類(lèi)各司其職,很清晰知道是干什么的,現(xiàn)在講一下spring-security原理及工作流程

圖片中各個(gè)類(lèi)的作用:
1 JwtUser類(lèi):實(shí)現(xiàn)Springsecurity的UserDetails類(lèi),此類(lèi)必須有三個(gè)屬性
private String username;
private String password;
//權(quán)限,類(lèi)如ROLE_ADMIN
private Collection<? extends GrantedAuthority> authorities;
2 JwtUtils:jwt工具類(lèi)
3 MD5Util:密碼加密工具類(lèi)
4 MyAccessDeniedHandler:實(shí)現(xiàn)Springsecurity的AccessDeniedHandler,Spring security權(quán)限不足處理類(lèi)
5 MyAuthenticationException:實(shí)現(xiàn)Springsecurity的AuthenticationEntryPoint,Spring security其他異常處理類(lèi),比如請(qǐng)求路徑不存在等
6 MyAuthenticationFailHandler: 實(shí)現(xiàn)Springsecurity的AuthenticationFailureHandler,Spring security登錄失敗處理類(lèi)
7 MyAuthenticationSuccessHandler:實(shí)現(xiàn)Springsecurity的AuthenticationSuccessHandler,Spring security登錄成功處理類(lèi)
8 MyJwtTokenFilter: 繼承Springsecurity的OncePerRequestFilter,token 過(guò)濾器,在這里解析token,拿到該用戶(hù)角色,設(shè)置到springsecurity的上下文環(huán)境中,讓springsecurity自動(dòng)判斷權(quán)限
9 MyUserDetailsService:實(shí)現(xiàn)Springsecurity的UserDetailsService,根據(jù)用戶(hù)名獲取數(shù)據(jù)庫(kù)該用戶(hù)信息,spring security在登錄時(shí)自動(dòng)調(diào)用
10 WebSecurityConfig: Spring security的總配置類(lèi),配置密碼驗(yàn)證規(guī)則、攔截的url、登錄接口地址、登錄成功與失敗后的處理器、各種異常處理器
總結(jié):
spring-security工作流程就是
1自動(dòng)識(shí)別登錄接口路徑(你配置的),自動(dòng)進(jìn)入賬戶(hù)密碼判斷方法,判斷規(guī)則可自定義,密碼驗(yàn)證通過(guò)后會(huì)進(jìn)入spring security提供的MyAuthenticationSuccessHandler類(lèi),在這里重寫(xiě)方法,用HttpServletResponse自定義返回給前端內(nèi)容,可以返回接口也可以返回html,隨你,我這里返回jwt。驗(yàn)證失敗同理。
這里說(shuō)下 Spring security的賬戶(hù)密碼判斷邏輯,WebSecurityConfig繼承WebSecurityConfigurerAdapter,注入configureAuthentication方法,重寫(xiě)matches方法自定義密碼驗(yàn)證規(guī)則,返回boolean
@Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
// 設(shè)置UserDetailsService 獲取user對(duì)象
.userDetailsService(this.userDetailsService)
// 自定義密碼驗(yàn)證方法
.passwordEncoder(new PasswordEncoder() {
//這個(gè)方法沒(méi)用
@Override
public String encode(CharSequence charSequence) {
return "";
}
//自定義密碼驗(yàn)證方法,charSequence:用戶(hù)輸入的密碼,s:我們查出來(lái)的數(shù)據(jù)庫(kù)密碼
@Override
public boolean matches(CharSequence charSequence, String s) {
String pass = MD5Util.string2MD5(charSequence.toString());
System.out.println("用戶(hù)輸入密碼:" + charSequence + "與數(shù)據(jù)庫(kù)相同?" + s.equals(pass));
return s.equals(pass);
}
});
}
2登錄成功后,前端攜帶jwt的token來(lái)訪問(wèn)接口,首先會(huì)進(jìn)入我們配置的MyJwtTokenFilter 類(lèi)繼承自O(shè)ncePerRequestFilter,這個(gè)OncePerRequestFilter就厲害了,可以說(shuō)是所有filter的基類(lèi),所以最先執(zhí)行,在這里我們寫(xiě)驗(yàn)證jwt的邏輯,驗(yàn)證通過(guò)后要告訴springsecurity,我們獲取到的用戶(hù)的權(quán)限,并把權(quán)限設(shè)置到springsecurity的上下文環(huán)境中,讓它來(lái)給我們做權(quán)限的判斷,這點(diǎn)最重要!?。?!是springsecurity和jwt的整合紐帶,主要代碼如下:
if(JwtUtils.isTokenExpired(Claims)){//token過(guò)期
System.out.println("token過(guò)期" + authToken);
}else{
System.out.println("token沒(méi)過(guò)期,放行" + authToken);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(UserDetails, null, UserDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
logger.info(String.format("Authenticated userDetail %s, setting security context", username));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
3springsecurity自動(dòng)驗(yàn)證權(quán)限后,如果權(quán)限不足,會(huì)進(jìn)入到我們配置的權(quán)限不足捕獲異常類(lèi)MyAccessDeniedHandler,其他錯(cuò)誤的話(huà)進(jìn)入另一個(gè)異常捕獲類(lèi)MyAuthenticationException,都可以自定義返回值
如何使用
具體見(jiàn)源碼的-- 說(shuō)明.txt
本項(xiàng)目是Spring security5+Jwt的基礎(chǔ)實(shí)例
功能:
1:驗(yàn)證用戶(hù)登錄賬號(hào)密碼是否正確,登錄地址可以自定義配置,密碼驗(yàn)證規(guī)則也是自定義,我這里只要是123456就行,驗(yàn)證成功與失敗分別有不同的類(lèi)來(lái)處理,登錄成功后生成JWT返回
測(cè)試方法:http://localhost:8080/user/login?username=adm&password=123456
2:配置攔截除了"/test/**"以外的所有請(qǐng)求,登錄地址配置好以后Spring security會(huì)自動(dòng)設(shè)置為不攔截
3:配置了各種攔截器,具體看代碼
使用方法
1登錄獲取jwt token(在Response Header里),POST http://localhost:8080/user/login?username=adm&password=123456
2根據(jù)token 調(diào)用ROLE_ADMIN權(quán)限接口, GET http://localhost:8080/role/aaa ,返回權(quán)限不足,,MyUserDetailsService默認(rèn)給用戶(hù)加了ROLE_USER權(quán)限
根據(jù)token 調(diào)用無(wú)權(quán)限注解的接口, GET http://localhost:8080/role/bbb ,返回正常結(jié)果
根據(jù)token 調(diào)用ROLE_USER權(quán)限接口, GET http://localhost:8080/role/ccc ,返回正常結(jié)果
3調(diào)用不存在的接口,返回捕獲異常http://localhost:8080/tessssst/error
歡迎大家討論點(diǎn)贊??
附:
JWT通用類(lèi) https://blog.csdn.net/qq_37636695/article/details/79265711
坑
跨域問(wèn)題,postman能拿到值,前端頁(yè)面拿不到
1//解決spring security 跨域問(wèn)題
https://blog.csdn.net/qq_35494808/article/details/82998135
2//解決X-Frame-Options: DENY問(wèn)題,jwt過(guò)期時(shí),自定義返回值時(shí),前端拿不到返回值,postman可以看到有的
在configure(HttpSecurity httpSecurity)里加上
.and().headers().frameOptions().disable();
3 在異常處理類(lèi)的HttpServletResponse里加Header
public static void httpHandle(int stats ,HttpServletRequest httpServletRequest, HttpServletResponse response, String str) throws
IOException {
/**
* 注意這里加上跨域的配置
*/
response.setHeader("Access-Control-Allow-Origin",httpServletRequest.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
response.setStatus(stats);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter printWriter = response.getWriter();
response.getWriter().write(str);
printWriter.flush();
}