這篇文章主要介紹了spring security和jwt整合的方法是什么的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇spring security和jwt整合的方法是什么文章都會(huì)有所收獲,下面我們一起來看看吧。
創(chuàng)新互聯(lián)建站主要從事成都網(wǎng)站制作、成都做網(wǎng)站、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)共青城,十多年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):13518219792
json web token (JWT),是為了在網(wǎng)絡(luò)環(huán)境中傳遞聲明而設(shè)計(jì)的一種基于JSON的開放標(biāo)準(zhǔn)(RFC 7519),該token 被設(shè)計(jì)為緊湊且安全的.特別使用于分布式站點(diǎn)的登陸(SSO)
場(chǎng)景.JWT一般被用來在服務(wù)提供者和服務(wù)認(rèn)證者之間傳遞身份信息,以便可以從服務(wù)器獲取資源.也可以增加一些額外的其它業(yè)務(wù)邏輯所必需的聲明信息.
該token可直接被用于認(rèn)證,也可用于被加密.
基于token的鑒權(quán)機(jī)制也是類似于http協(xié)議無狀態(tài)的,它不需要在服務(wù)段保留用戶的認(rèn)證信息或者鑒權(quán)信息.這就意味著基于token認(rèn)證機(jī)制的用戶就不必考慮在哪一臺(tái)服務(wù)器登錄了.
這就為應(yīng)用的擴(kuò)展提供了遍歷.
認(rèn)證流程:
這個(gè)token必須在每次請(qǐng)求時(shí)傳遞給服務(wù)端,它應(yīng)該保存在請(qǐng)求頭里面.另外,服務(wù)器端要支持 CORS(跨來源資源共享策略) ,一般我們?cè)诜?wù)器上這么做就可以了, Access-Control-Allow-Origin: *
jwt的三個(gè)組成部分共同構(gòu)成了一個(gè) 簽名信息 signature
**這個(gè)部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串.
然后通過header中聲明的加密方式進(jìn)行加鹽secret組合加密,然后就構(gòu)成了jwt的第三部分。**
注意:secret是保存在服務(wù)器端的,jwt的簽發(fā)生成也是在服務(wù)器端的,secret就是用來進(jìn)行jwt的簽發(fā)和jwt的驗(yàn)證,
所以,它就是你服務(wù)端的私鑰,在任何場(chǎng)景都不應(yīng)該流露出去。一旦客戶端得知這個(gè)secret, 那就意味著客戶端是可以自我簽發(fā)jwt了。
一般是在請(qǐng)求頭里加入Authorization,并加上Bearer標(biāo)注:如下:
fetch('api/user/1', { headers: { 'Authorization': 'Bearer '
我們之前介紹過,Spring security是基于過濾器(Filter)的,使用過濾器我們可以很容易的攔截某些請(qǐng)求.
因此通過上面對(duì)jwt的了解,我們就可以在過濾器中處理token的生成和校驗(yàn).
大致流程如下:
1.當(dāng)用戶進(jìn)行提交登陸表單時(shí),自定義一個(gè)攔截器JWTLoginFilter進(jìn)行表單參數(shù)的獲取.
2.驗(yàn)證提交的用戶名密碼是否正確.
3.如果登陸成功,使用jwt頒發(fā)一個(gè)token給客戶端,之后的客戶端請(qǐng)求都要帶上這個(gè)token.
4.token驗(yàn)證:再自定義一個(gè)過濾器JWTAuthenticationFilter,當(dāng)用戶訪問需要認(rèn)證的請(qǐng)求時(shí),攔截該請(qǐng)求,并進(jìn)行token校驗(yàn).
我們?yōu)榱撕?jiǎn)化開發(fā)使用spring boot進(jìn)行項(xiàng)目的快速搭建.需要引入如下依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
之后我們創(chuàng)建一個(gè)controller進(jìn)行不同級(jí)別的驗(yàn)證.
/** * @author itguang * @create @RestController public class UserController @Autowired private UserRepository applicationUserRepository; @RequestMapping("/hello") public String hello(){ return "hello"; } @RequestMapping("/userList") public Map<String, Object> userList(){ List<User> myUsers = applicationUserRepository.findAll(); Map<String, Object> map = new HashMap<String, Object>(); map.put("users",myUsers); return map; } @RequestMapping("/admin") public String admin(){ return "admin"; } }
接下來就是配置我們的安全管理類 SecurityConfig :
/** * @author itguang * @create @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; @Autowired private UserDetailsServiceImpl userDetailsService; @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { // auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder); // 使用自定義身份驗(yàn)證組件 auth.authenticationProvider(new CustomAuthenticationProvider(userDetailsService,bCryptPasswordEncoder)); } @Override protected void configure(HttpSecurity http) throws Exception { //禁用 csrf http.cors().and().csrf().disable().authorizeRequests() //允許以下請(qǐng)求 .antMatchers("/hello").permitAll() // 所有請(qǐng)求需要身份認(rèn)證 .anyRequest().authenticated() .and() //驗(yàn)證登陸 .addFilter(new JWTLoginFilter(authenticationManager())) //驗(yàn)證token .addFilter(new
可以看到我們的Security繼承了 WebSecurityConfigurerAdapter ,關(guān)于WebSecurityConfigurerAdapter我們之前的文章已經(jīng)介紹過,
我們重點(diǎn)關(guān)注的是重載的兩個(gè) configure() 方法.
configure(HttpSecurity http):這個(gè)方法配置了對(duì)請(qǐng)求的攔截配置,在這里我們又添加了兩個(gè)自定義的過濾器,JWTLoginFilter 和JWTAuthenticationFilter,
分別負(fù)責(zé)登錄時(shí)用戶名密碼的驗(yàn)證,和攔截請(qǐng)求時(shí)對(duì)token的驗(yàn)證.
configure(AuthenticationManagerBuilder auth):這個(gè)方法有點(diǎn)奇怪,我們并沒有使用之前介紹幾種的用戶存儲(chǔ),而是使用了一個(gè)authenticationProvider()
方法,并傳入了一個(gè)我們自定義的 AuthenticationProvider 類型的對(duì)象作為參數(shù).稍后我們會(huì)詳細(xì)介紹這個(gè)類到底是什么.
/** * @author itguang * @create public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter private AuthenticationManager authenticationManager; public JWTLoginFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } /** * 接收并解析用戶登陸信息 /login, *為已驗(yàn)證的用戶返回一個(gè)已填充的身份驗(yàn)證令牌,表示成功的身份驗(yàn)證 *返回null,表明身份驗(yàn)證過程仍在進(jìn)行中。在返回之前,實(shí)現(xiàn)應(yīng)該執(zhí)行完成該過程所需的任何額外工作。 *如果身份驗(yàn)證過程失敗,就拋出一個(gè)AuthenticationException * * * @param request 從中提取參數(shù)并執(zhí)行身份驗(yàn)證 * @param response 如果實(shí)現(xiàn)必須作為多級(jí)身份驗(yàn)證過程的一部分(比如OpenID)進(jìn)行重定向,則可能需要響應(yīng) * @return 身份驗(yàn)證的用戶令牌,如果身份驗(yàn)證不完整,則為null。 * @throws @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //得到用戶登陸信息,并封裝到 Authentication 中,供自定義用戶組件使用. String username = request.getParameter("username"); String password = request.getParameter("password"); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); ArrayList<GrantedAuthorityImpl> authorities = new ArrayList<>(); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password, authorities); //authenticate()接受一個(gè)token參數(shù),返回一個(gè)完全經(jīng)過身份驗(yàn)證的對(duì)象,包括證書. // 這里并沒有對(duì)用戶名密碼進(jìn)行驗(yàn)證,而是使用 AuthenticationProvider 提供的 authenticate 方法返回一個(gè)完全經(jīng)過身份驗(yàn)證的對(duì)象,包括證書. // Authentication authenticate = authenticationManager.authenticate(authenticationToken); //UsernamePasswordAuthenticationToken 是 Authentication 的實(shí)現(xiàn)類 return authenticationToken; } /** * 登陸成功后,此方法會(huì)被調(diào)用,因此我們可以在次方法中生成token,并返回給客戶端 * * @param request * @param response * @param chain * @param @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) { String token = Jwts.builder() .setSubject(authResult.getName()) //有效期兩小時(shí) .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 2 * 1000)) //采用什么算法是可以自己選擇的,不一定非要采用HS512 .signWith(SignatureAlgorithm.HS512, "MyJwtSecret") .compact(); response.addHeader("token", "Bearer "
我們可以看到 JWTLoginFilter 繼承了 UsernamePasswordAuthenticationFilter,
并且重寫了它的 attemptAuthentication() 方法和 successfulAuthentication() 方法.
在 attemptAuthentication()方法中,我們就可以得到 /login 提交的用戶名和密碼信息,但這里我們并沒有返回一個(gè)認(rèn)證后的 Authentication,
這是為什么呢?原因就在于,我們?cè)?SecurityConfigure 的方法中,使用了一個(gè)自定義的 AuthenticationProvider 實(shí)現(xiàn)類,如:
@Override public void configure(AuthenticationManagerBuilder auth) throws Exception { // auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder); // 使用自定義身份驗(yàn)證組件 auth.authenticationProvider(new
那么 AuthenticationProvider 用來干嘛的呢? 查看他的源碼可以發(fā)現(xiàn):
public interface AuthenticationProvider /** * 驗(yàn)證登錄信息,若登陸成功,設(shè)置 Authentication * * @param authentication * @return 一個(gè)完全經(jīng)過身份驗(yàn)證的對(duì)象,包括憑證。 * 如果AuthenticationProvider無法支持已通過的身份驗(yàn)證對(duì)象的身份驗(yàn)證,則可能返回null。 * 在這種情況下,將會(huì)嘗試支持下一個(gè)身份驗(yàn)證類的驗(yàn)證提供者。 * @throws Authentication authenticate(Authentication authentication) throws AuthenticationException; /** * 是否可以提供輸入類型的認(rèn)證服務(wù) * * 如果這個(gè)AuthenticationProvider支持指定的身份驗(yàn)證對(duì)象,那么返回true。 * 返回true并不能保證身份驗(yàn)證提供者能夠?qū)ι矸蒡?yàn)證類的實(shí)例進(jìn)行身份驗(yàn)證。 * 它只是表明它可以支持對(duì)它進(jìn)行更深入的評(píng)估。身份驗(yàn)證提供者仍然可以從身份驗(yàn)證(身份驗(yàn)證)方法返回null, * 以表明應(yīng)該嘗試另一個(gè)身份驗(yàn)證提供者。在運(yùn)行時(shí)管理器的運(yùn)行時(shí),可以選擇具有執(zhí)行身份驗(yàn)證的身份驗(yàn)證提供者。 * * @param authentication * @return boolean
AuthenticationProvider(身份驗(yàn)證提供者) 顧名思義,可以提供一個(gè) Authentication 供Spring Security的上下文使用.
通過 supports 方法我們對(duì)特定的 Authentication進(jìn)行認(rèn)證,如果返回 true,就交給 authenticate(Authentication authentication) 方法,
此方法一個(gè)完全經(jīng)過身份驗(yàn)證的對(duì)象,包括憑證。
如下我們自定義的 CustomAuthenticationProvider:
/** * AuthenticationProvider(身份驗(yàn)證提供者) 顧名思義,可以提供一個(gè) Authentication 供Spring Security的上下文使用, * * @author itguang * @create public class CustomAuthenticationProvider implements AuthenticationProvider private UserDetailsService userDetailsService; private BCryptPasswordEncoder bCryptPasswordEncoder; public CustomAuthenticationProvider(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) { this.userDetailsService = userDetailsService; this.bCryptPasswordEncoder = bCryptPasswordEncoder; } /** * 是否可以提供輸入類型的認(rèn)證服務(wù) * <p> * 如果這個(gè)AuthenticationProvider支持指定的身份驗(yàn)證對(duì)象,那么返回true。 * 返回true并不能保證身份驗(yàn)證提供者能夠?qū)ι矸蒡?yàn)證類的實(shí)例進(jìn)行身份驗(yàn)證。 * 它只是表明它可以支持對(duì)它進(jìn)行更深入的評(píng)估。身份驗(yàn)證提供者仍然可以從身份驗(yàn)證(身份驗(yàn)證)方法返回null, * 以表明應(yīng)該嘗試另一個(gè)身份驗(yàn)證提供者。在運(yùn)行時(shí)管理器的運(yùn)行時(shí),可以選擇具有執(zhí)行身份驗(yàn)證的身份驗(yàn)證提供者。 * * @param authentication * @return @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } /** * 驗(yàn)證登錄信息,若登陸成功,設(shè)置 Authentication * * @param authentication * @return 一個(gè)完全經(jīng)過身份驗(yàn)證的對(duì)象,包括憑證。 * 如果AuthenticationProvider無法支持已通過的身份驗(yàn)證對(duì)象的身份驗(yàn)證,則可能返回null。 * 在這種情況下,將會(huì)嘗試支持下一個(gè)身份驗(yàn)證類的驗(yàn)證提供者。 * @throws @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 獲取認(rèn)證的用戶名 & 密碼 String username = authentication.getName(); String password = authentication.getCredentials().toString(); //通過用戶名從數(shù)據(jù)庫中查詢?cè)撚脩? UserDetails userDetails = userDetailsService.loadUserByUsername(username); //判斷密碼(這里是md5加密方式)是否正確 String dbPassword = userDetails.getPassword(); String encoderPassword = DigestUtils.md5DigestAsHex(password.getBytes()); if (!dbPassword.equals(encoderPassword)) { throw new UsernameIsExitedException("密碼錯(cuò)誤"); } // 還可以從數(shù)據(jù)庫中查出該用戶所擁有的權(quán)限,設(shè)置到 authorities 中去,這里模擬數(shù)據(jù)庫查詢. ArrayList<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new GrantedAuthorityImpl("ADMIN")); Authentication auth = new UsernamePasswordAuthenticationToken(username, password, authorities); return
可見我們?cè)谶@個(gè) AuthenticationProvider 中對(duì) UsernamePasswordAuthenticationToken 進(jìn)行認(rèn)證,
在 authenticate(Authentication authentication)方法中, authentication 就是 我們之前返回的 UsernamePasswordAuthenticationToken,我們可以得到登陸的用戶名和密碼,進(jìn)行真正的認(rèn)證.
如果認(rèn)證成功 就給改 UsernamePasswordAuthenticationToken 設(shè)置對(duì)應(yīng)的權(quán)限,最后把已經(jīng)認(rèn)證的 UsernamePasswordAuthenticationToken 返回即可.
還有我們?cè)谕ㄟ^用戶名從數(shù)據(jù)庫查找用戶時(shí),返回了一個(gè) UserDetails 對(duì)象,關(guān)于UserdDetails對(duì)象,我們之前的文章已經(jīng)介紹過,不懂得可以去查看一下.
最后,當(dāng) CustomAuthenticationProvider 認(rèn)證成功之后,JWTLoginFilter 中的 successfulAuthentication() 方法機(jī)會(huì)執(zhí)行,因此我們就可以在這里設(shè)置token了,如下:
/** * 登陸成功后,此方法會(huì)被調(diào)用,因此我們可以在次方法中生成token,并返回給客戶端 * * @param request * @param response * @param chain * @param @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) { String token = Jwts.builder() .setSubject(authResult.getName()) //有效期兩小時(shí) .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 2 * 1000)) //采用什么算法是可以自己選擇的,不一定非要采用HS512 .signWith(SignatureAlgorithm.HS512, "MyJwtSecret") .compact(); response.addHeader("token", "Bearer "
我們使用JWT構(gòu)造了一個(gè)token字符串,并把它放在了http請(qǐng)求頭中返回給了客戶端.
至此我們的登陸認(rèn)證并返回 token就已經(jīng)完成了,接下來就是客戶端攜帶這已經(jīng)獲得token訪問需要認(rèn)證的資源時(shí),我們需要對(duì)改token進(jìn)行驗(yàn)證了.
/** * token校驗(yàn) * * @author itguang * @create public class JWTAuthenticationFilter extends BasicAuthenticationFilter public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } /** * 在此方法中檢驗(yàn)客戶端請(qǐng)求頭中的token, * 如果存在并合法,就把token中的信息封裝到 Authentication 類型的對(duì)象中, * 最后使用 SecurityContextHolder.getContext().setAuthentication(authentication); 改變或刪除當(dāng)前已經(jīng)驗(yàn)證的 pricipal * * @param request * @param response * @param chain * @throws IOException * @throws @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String token = request.getHeader("token"); //判斷是否有token if (token == null || !token.startsWith("Bearer ")) { chain.doFilter(request, response); return; } UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authenticationToken); //放行 chain.doFilter(request, response); } /** * 解析token中的信息,并判斷是否過期 */ private UsernamePasswordAuthenticationToken getAuthentication(String token) { Claims claims = Jwts.parser().setSigningKey("MyJwtSecret") .parseClaimsJws(token.replace("Bearer ", "")) .getBody(); //得到用戶名 String username = claims.getSubject(); //得到過期時(shí)間 Date expiration = claims.getExpiration(); //判斷是否過期 Date now = new Date(); if (now.getTime() > expiration.getTime()) { throw new UsernameIsExitedException("該賬號(hào)已過期,請(qǐng)重新登陸"); } if (username != null) { return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>()); } return null; } }
由此可以看到 JWTAuthenticationFilter 繼承了 BasicAuthenticationFilter,
BasicAuthenticationFilter 用來處理一個(gè)HTTP請(qǐng)求的基本授權(quán)標(biāo)頭,將結(jié)果放入安全上下文。
總之,這個(gè)過濾器負(fù)責(zé)處理任何具有HTTP請(qǐng)求頭的請(qǐng)求的請(qǐng)求,以及一個(gè)基本的身份驗(yàn)證方案和一個(gè)base64編碼的用戶名:密碼令牌。
如果身份驗(yàn)證成功,那么最終的身份驗(yàn)證對(duì)象將被放入安全上下文。
因此我們就可以繼承 BasicAuthenticationFilter 并重寫 doFilterInternal()方法,在該方法中進(jìn)行token的驗(yàn)證,如果驗(yàn)證成功,將結(jié)果放入安全上下文,如:
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
到此,我們就使用Spring Security + JWT ,搭建了一個(gè)安全的 resultful api ,接下來我們就進(jìn)行簡(jiǎn)單的測(cè)試,這里我是用postman,這是一個(gè)非常好用的 http 調(diào)試工具.
我們現(xiàn)在數(shù)據(jù)庫的users表中插入一條用戶信息,用戶名:itguang 密碼: 123456,
接下來,打開post滿,訪問 localhost/login?username=itguang&password=123456
如下:
我們可以看到響應(yīng)頭中多了一個(gè)tokenproperties
token →Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJpdGd1YW5nIiwiZXhwIjoxNTE0OTU2NjI3fQ.PIiH7dRrVgPc88kOPtGzvrqZf5l87FRe3h7s9YZVb2zkL_XwRc_v3uhn23bmKqu7G0pSZngdnX0rh_kT1YDwww
這就是我們使用jwt生成的token,現(xiàn)在是加密狀態(tài),接下來我們?cè)僭L問 localhost/admin ,并把這個(gè)token放到 請(qǐng)求頭中,如下:
會(huì)看到返回了正確的字符串,但是如果我們不帶該token值呢?
瀏覽器訪問: http://localhost/admin ,會(huì)發(fā)現(xiàn)
403,明顯的沒有權(quán)限禁止訪問,這正是我們想要的結(jié)果.
關(guān)于“spring security和jwt整合的方法是什么”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“spring security和jwt整合的方法是什么”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
新聞名稱:springsecurity和jwt整合的方法是什么
分享URL:http://muchs.cn/article30/pieoso.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站內(nèi)鏈、面包屑導(dǎo)航、標(biāo)簽優(yōu)化、響應(yīng)式網(wǎng)站、網(wǎng)站策劃、App設(shè)計(jì)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)