激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語(yǔ)言|JavaScript|易語(yǔ)言|vb.net|

服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - SpringSecurity構(gòu)建基于JWT的登錄認(rèn)證實(shí)現(xiàn)

SpringSecurity構(gòu)建基于JWT的登錄認(rèn)證實(shí)現(xiàn)

2021-08-05 11:11locotor在掘金 Java教程

這篇文章主要介紹了SpringSecurity構(gòu)建基于JWT的登錄認(rèn)證實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

最近項(xiàng)目的登錄驗(yàn)證部分,采用了 JWT 驗(yàn)證的方式。并且既然采用了 Spring Boot 框架,驗(yàn)證和權(quán)限管理這部分,就自然用了 Spring Security。這里記錄一下具體實(shí)現(xiàn)。
在項(xiàng)目采用 JWT 方案前,有必要先了解它的特性和適用場(chǎng)景,畢竟軟件工程里,沒(méi)有銀彈。只有合適的場(chǎng)景,沒(méi)有萬(wàn)精油的方案。

一言以蔽之,JWT 可以攜帶非敏感信息,并具有不可篡改性。可以通過(guò)驗(yàn)證是否被篡改,以及讀取信息內(nèi)容,完成網(wǎng)絡(luò)認(rèn)證的三個(gè)問(wèn)題:“你是誰(shuí)”、“你有哪些權(quán)限”、“是不是冒充的”。

為了安全,使用它需要采用 Https 協(xié)議,并且一定要小心防止用于加密的密鑰泄露。

采用 JWT 的認(rèn)證方式下,服務(wù)端并不存儲(chǔ)用戶狀態(tài)信息,有效期內(nèi)無(wú)法廢棄,有效期到期后,需要重新創(chuàng)建一個(gè)新的來(lái)替換。
所以它并不適合做長(zhǎng)期狀態(tài)保持,不適合需要用戶踢下線的場(chǎng)景,不適合需要頻繁修改用戶信息的場(chǎng)景。因?yàn)橐鉀Q這些問(wèn)題,總是需要額外查詢數(shù)據(jù)庫(kù)或者緩存,或者反復(fù)加密解密,強(qiáng)扭的瓜不甜,不如直接使用 Session。不過(guò)作為服務(wù)間的短時(shí)效切換,還是非常合適的,就比如 OAuth 之類的。

目標(biāo)功能點(diǎn)

 

通過(guò)填寫用戶名和密碼登錄。

  • 驗(yàn)證成功后, 服務(wù)端生成 JWT 認(rèn)證 token, 并返回給客戶端。
  • 驗(yàn)證失敗后返回錯(cuò)誤信息。
  • 客戶端在每次請(qǐng)求中攜帶 JWT 來(lái)訪問(wèn)權(quán)限內(nèi)的接口。

每次請(qǐng)求驗(yàn)證 token 有效性和權(quán)限,在無(wú)有效 token 時(shí)拋出 401 未授權(quán)錯(cuò)誤。
當(dāng)發(fā)現(xiàn)請(qǐng)求帶著的 token 有效期快到了的時(shí)候,返回特定狀態(tài)碼,重新請(qǐng)求一個(gè)新 token。

準(zhǔn)備工作

 

引入 Maven 依賴

針對(duì)這個(gè)登錄驗(yàn)證的實(shí)現(xiàn),需要引入 Spring Security、jackson、java-jwt 三個(gè)包。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-security</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.fasterxml.jackson.core</groupId>
  7. <artifactId>jackson-core</artifactId>
  8. <version>2.12.1</version>
  9. </dependency>
  10. <dependency>
  11. <groupId>com.auth0</groupId>
  12. <artifactId>java-jwt</artifactId>
  13. <version>3.12.1</version>
  14. </dependency>

配置 DAO 數(shù)據(jù)層

要驗(yàn)證用戶前,自然是先要?jiǎng)?chuàng)建用戶實(shí)體對(duì)象,以及獲取用戶的服務(wù)類。不同的是,這兩個(gè)類需要實(shí)現(xiàn) Spring Security 的接口,以便將它們集成到驗(yàn)證框架中。

User

用戶實(shí)體類需要實(shí)現(xiàn) ”UserDetails“ 接口,這個(gè)接口要求實(shí)現(xiàn) getUsername、getPassword、getAuthorities 三個(gè)方法,用以獲取用戶名、密碼和權(quán)限。以及 isAccountNonExpired```isAccountNonLocked、isCredentialsNonExpired、isEnabled 這四個(gè)判斷是否是有效用戶的方法,因?yàn)楹万?yàn)證無(wú)關(guān),所以先都返回 true。這里圖方便,用了 lombok。

  1. @Data
  2. public class User implements UserDetails {
  3.  
  4. private static final long serialVersionUID = 1L;
  5.  
  6. private String username;
  7.  
  8. private String password;
  9.  
  10. private Collection<? extends GrantedAuthority> authorities;
  11.  
  12. ...
  13. }

UserService

用戶服務(wù)類需要實(shí)現(xiàn) “UserDetailsService” 接口,這個(gè)接口非常簡(jiǎn)單,只需要實(shí)現(xiàn) loadUserByUsername(String username) 這么一個(gè)方法。這里使用了 MyBatis 來(lái)連接數(shù)據(jù)庫(kù)獲取用戶信息。

  1. @Service
  2. public class UserService implements UserDetailsService {
  3.  
  4. @Autowired
  5. UserMapper userMapper;
  6.  
  7. @Override
  8. @Transactional
  9. public User loadUserByUsername(String username) {
  10. return userMapper.getByUsername(username);
  11. }
  12.  
  13. ...
  14. }

創(chuàng)建 JWT 工具類

這個(gè)工具類主要負(fù)責(zé) token 的生成,驗(yàn)證,從中取值。

  1. @Component
  2. public class JwtTokenProvider {
  3.  
  4. private static final long JWT_EXPIRATION = 5 * 60 * 1000L; // 五分鐘過(guò)期
  5.  
  6. public static final String TOKEN_PREFIX = "Bearer "; // token 的開(kāi)頭字符串
  7.  
  8. private String jwtSecret = "XXX 密鑰,打死也不能告訴別人";
  9.  
  10. ...
  11. }

生成 JWT:從以通過(guò)驗(yàn)證的認(rèn)證對(duì)象中,獲取用戶信息,然后用指定加密方式,以及過(guò)期時(shí)間生成 token。這里簡(jiǎn)單的只加了用戶名這一個(gè)信息到 token 中:

  1. public String generateToken(Authentication authentication) {
  2. User userPrincipal = (User) authentication.getPrincipal(); // 獲取用戶對(duì)象
  3. Date expireDate = new Date(System.currentTimeMillis() + JWT_EXPIRATION); // 設(shè)置過(guò)期時(shí)間
  4. try {
  5. Algorithm algorithm = Algorithm.HMAC256(jwtSecret); // 指定加密方式
  6. return JWT.create().withExpiresAt(expireDate).withClaim("username", userPrincipal.getUsername())
  7. .sign(algorithm); // 簽發(fā) JWT
  8. } catch (JWTCreationException jwtCreationException) {
  9. return null;
  10. }
  11. }

驗(yàn)證 JWT:指定和簽發(fā)相同的加密方式,驗(yàn)證這個(gè) token 是否是本服務(wù)器簽發(fā),是否篡改,或者已過(guò)期。

  1. public boolean validateToken(String authToken) {
  2. try {
  3. Algorithm algorithm = Algorithm.HMAC256(jwtSecret); // 和簽發(fā)保持一致
  4. JWTVerifier verifier = JWT.require(algorithm).build();
  5. verifier.verify(authToken);
  6. return true;
  7. } catch (JWTVerificationException jwtVerificationException) {
  8. return false;
  9. }
  10. }

獲取荷載信息:從 token 的荷載部分里解析用戶名信息,這部分是 md5 編碼的,屬于公開(kāi)信息。

  1. public String getUsernameFromJWT(String authToken) {
  2. try {
  3. DecodedJWT jwt = JWT.decode(authToken);
  4. return jwt.getClaim("username").asString();
  5. } catch (JWTDecodeException jwtDecodeException) {
  6. return null;
  7. }
  8. }

登錄

 

登錄部分需要?jiǎng)?chuàng)建三個(gè)文件:負(fù)責(zé)登錄接口處理的攔截器,登陸成功或者失敗的處理類。

LoginFilter

Spring Security 默認(rèn)自帶表單登錄,負(fù)責(zé)處理這個(gè)登錄驗(yàn)證過(guò)程的過(guò)濾器叫“UsernamePasswordAuthenticationFilter”,不過(guò)它只支持表單傳值,這里用自定義的類繼承它,使其能夠支持 JSON 傳值,負(fù)責(zé)登錄驗(yàn)證接口。
這個(gè)攔截器只需要負(fù)責(zé)從請(qǐng)求中取值即可,驗(yàn)證工作 Spring Security 會(huì)幫我們處理好。

  1. public class LoginFilter extends UsernamePasswordAuthenticationFilter {
  2.  
  3. @Override
  4. public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
  5. if (!request.getMethod().equals("POST")) {
  6. throw new AuthenticationServiceException("登錄接口方法不支持: " + request.getMethod());
  7. }
  8. if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
  9. Map<String, String> loginData = new HashMap<>();
  10. try {
  11. loginData = new ObjectMapper().readValue(request.getInputStream(), Map.class);
  12. } catch (IOException e) {
  13. }
  14. String username = loginData.get(getUsernameParameter());
  15. String password = loginData.get(getPasswordParameter());
  16. if (username == null) {
  17. username = "";
  18. }
  19. if (password == null) {
  20. password = "";
  21. }
  22. username = username.trim();
  23. UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,
  24. password);
  25. setDetails(request, authRequest);
  26. return this.getAuthenticationManager().authenticate(authRequest);
  27. } else {
  28. return super.attemptAuthentication(request, response);
  29. }
  30. }
  31.  
  32. }

LoginSuccessHandler

負(fù)責(zé)在登錄成功后,生成 JWT 給前端。

  1. @Component
  2. public class LoginSuccessHandler implements AuthenticationSuccessHandler {
  3.  
  4. @Autowired
  5. private JwtTokenProvider jwtTokenProvider;
  6.  
  7. @Override
  8. public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
  9. Authentication authentication) throws IOException, ServletException {
  10.  
  11. ResponseData responseData = new ResponseData();
  12. String token = jwtTokenProvider.generateToken(authentication);
  13. responseData.setData(JwtTokenProvider.TOKEN_PREFIX + token);
  14. response.setContentType("application/json;charset=utf-8");
  15. ObjectMapper mapper = new ObjectMapper();
  16. mapper.writeValue(response.getWriter(), responseData);
  17. }
  18.  
  19. }

LoginFailureHandler

驗(yàn)證失敗后,返回錯(cuò)誤信息。

  1. @Component
  2. public class LoginFailureHandler implements AuthenticationFailureHandler {
  3.  
  4. @Override
  5. public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
  6. AuthenticationException exception) throws IOException, ServletException {
  7. response.setContentType("application/json;charset=utf-8");
  8. ResponseData respBean = setResponseData(exception);
  9. ObjectMapper mapper = new ObjectMapper();
  10. mapper.writeValue(response.getWriter(), respBean);
  11. }
  12.  
  13. private ResponseData setResponseData(AuthenticationException exception) {
  14. if (exception instanceof LockedException) {
  15. return ResponseData.build("用戶已被鎖定");
  16. } else if (exception instanceof CredentialsExpiredException) {
  17. return ResponseData.build("密碼已過(guò)期");
  18. } else if (exception instanceof AccountExpiredException) {
  19. return ResponseData.build("用戶名已過(guò)期");
  20. } else if (exception instanceof DisabledException) {
  21. return ResponseData.build("賬戶不可用");
  22. } else if (exception instanceof BadCredentialsException) {
  23. return ResponseData.build("驗(yàn)證失敗");
  24. }
  25. return ResponseData.build("登錄失敗,請(qǐng)聯(lián)系管理員");
  26. }
  27.  
  28. }

驗(yàn)證

 

在成功登陸后,前端在每次發(fā)起請(qǐng)求時(shí)攜帶簽發(fā)的 JWT,讓服務(wù)端能識(shí)別這是已登錄的用戶。
同時(shí),如果未攜帶 JWT,或攜帶的 token 過(guò)期,或者非法,用單獨(dú)的處理類返回錯(cuò)誤信息。

JwtAuthenticationFilter

負(fù)責(zé)在每次請(qǐng)求中,解析請(qǐng)求頭中的 JWT,從中取得用戶信息,生成驗(yàn)證對(duì)象傳遞給下一個(gè)過(guò)濾器。

  1. @Component
  2. public class JwtAuthenticationFilter extends OncePerRequestFilter {
  3.  
  4. @Autowired
  5. private JwtTokenProvider jwtProvider;
  6.  
  7. @Override
  8. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
  9. throws ServletException, IOException {
  10. try {
  11. String jwt = getJwtFromRequest(request);
  12. UsernamePasswordAuthenticationToken authentication = verifyToken(jwt);
  13. if (authentication != null) {
  14. authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  15. }
  16. SecurityContextHolder.getContext().setAuthentication(authentication);
  17. } catch (Exception e) {
  18. logger.error("無(wú)法給 Security 上下文設(shè)置用戶驗(yàn)證對(duì)象", e);
  19. }
  20.  
  21. filterChain.doFilter(request, response);
  22. }
  23.  
  24. private String getJwtFromRequest(HttpServletRequest request) {
  25. String bearerToken = request.getHeader("Authorization");
  26. if (bearerToken == null || !bearerToken.startsWith(JwtTokenProvider.TOKEN_PREFIX)) {
  27. logger.info("請(qǐng)求頭不含 JWT token,調(diào)用下個(gè)過(guò)濾器");
  28. return null;
  29. }
  30.  
  31. return bearerToken.split(" ")[1].trim();
  32. }
  33.  
  34. // 驗(yàn)證token,并生成認(rèn)證后的token
  35. private UsernamePasswordAuthenticationToken verifyToken(String token) {
  36. if (token == null) {
  37. return null;
  38. }
  39. // 認(rèn)證失敗,返回null
  40. if (!jwtProvider.validateToken(token)) {
  41. return null;
  42. }
  43. // 提取用戶名
  44. String username = jwtProvider.getUsernameFromJWT(token);
  45. UserDetails userDetails = new User(username);
  46.  
  47. // 構(gòu)建認(rèn)證過(guò)的token
  48. return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
  49. }
  50. }

AuthenticationEntryPoint

這個(gè)類就比較簡(jiǎn)單,只是在驗(yàn)證不通過(guò)后,返回 401 響應(yīng),并記錄錯(cuò)誤信息。

  1. @Component
  2. public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
  3.  
  4. private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
  5.  
  6. @Override
  7. public void commence(HttpServletRequest request, HttpServletResponse response,
  8. AuthenticationException authException) throws IOException, ServletException {
  9. logger.error("驗(yàn)證為通過(guò). 提示信息 - {}", authException.getMessage());
  10. response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
  11. }
  12.  
  13. }

集中配置

 

Spring Security 的功能是通過(guò)一系列的過(guò)濾器鏈實(shí)現(xiàn)的,而配置整個(gè) Spring Security,只需要統(tǒng)一在一個(gè)類中配置即可。
現(xiàn)在咱們就創(chuàng)建這個(gè)類,繼承自 “WebSecurityConfigurerAdapter”,把上面準(zhǔn)備好的各種文件,一一配置進(jìn)去。
首先是通過(guò)注解,設(shè)置打開(kāi)全局的 Spring Security 功能,并通過(guò)依賴注入,引入剛剛創(chuàng)建的類。

  1. @Configuration
  2. @EnableWebSecurity
  3. public class KanpmSecurityConfig extends WebSecurityConfigurerAdapter {
  4.  
  5. @Autowired
  6. UserDetailsService userDetailsService;
  7.  
  8. @Autowired
  9. private JwtAuthenticationEntryPoint unauthorizedHandler;
  10.  
  11. @Autowired
  12. private JwtAuthenticationFilter jwtAuthenticationFilter;
  13.  
  14. @Bean
  15. public LoginFilter loginFilter(LoginSuccessHandler loginSuccessHandler, LoginFailureHandler loginFailureHandler)
  16. throws Exception {
  17. LoginFilter loginFilter = new LoginFilter();
  18. loginFilter.setAuthenticationSuccessHandler(loginSuccessHandler);
  19. loginFilter.setAuthenticationFailureHandler(loginFailureHandler);
  20. loginFilter.setAuthenticationManager(authenticationManagerBean());
  21. loginFilter.setFilterProcessesUrl("/auth/login");
  22. return loginFilter;
  23. }
  24.  
  25. @Bean
  26. @Override
  27. public AuthenticationManager authenticationManagerBean() throws Exception {
  28. return super.authenticationManagerBean();
  29. }
  30.  
  31. @Bean
  32. public PasswordEncoder passwordEncoder() {
  33. return new BCryptPasswordEncoder();
  34. }
  35.  
  36. ...
  37. }

接著,再把用戶獲取服務(wù)類和加密方式,配置到 Spring Security 中去,讓它知道如何去驗(yàn)證登錄。

  1. @Override
  2. public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
  3. authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
  4. }

最后,將JWT過(guò)濾器放入過(guò)濾器鏈中,用自定義的登錄過(guò)濾器替代默認(rèn)的 “UsernamePasswordAuthenticationFilter”,完成功能。

  1. @Override
  2. protected void configure(HttpSecurity http) throws Exception {
  3. http.csrf().disable().anyRequest().authenticated().and()
  4. .exceptionHandling().authenticationEntryPoint(unauthorizedHandler);
  5.  
  6. http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
  7. .addFilterAt(loginFilter(new LoginSuccessHandler(), new LoginFailureHandler()),
  8. UsernamePasswordAuthenticationFilter.class);
  9. }

到此這篇關(guān)于SpringSecurity構(gòu)建基于JWT的登錄認(rèn)證實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringSecurity JWT登錄認(rèn)證內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 在线观看日韩av电影 | 91成人在线免费视频 | 久久精品中文字幕一区二区 | 中国女警察一级毛片视频 | 欧美 日韩 亚洲 中文 | 五月天影院,久久综合, | 九九热这里只有精品8 | 免费观看欧美一级片 | 国产精品久久久久久久久久电影 | 日本免费成人网 | 国产电影精品久久 | 狠狠操在线观看 | 黄色av网站免费看 | 精品成人一区二区三区 | 高清视频一区二区 | 亚洲资源在线 | 欧美精品一区二区三区四区 | 亚洲一二三久久 | 性色av一区二区三区四区 | 国模论坛 | 免费观看的毛片手机视频 | 欧美中文日韩 | 久久99在线| 免费人成在线观看网站 | 日韩一级片一区二区三区 | 国产高潮国产高潮久久久91 | 久久久久免费精品 | 久久恋 | 日本精品视频一区二区三区四区 | 电影av在线 | 免费视频aaa | 国产日韩大片 | 欧美日韩精品一区二区三区蜜桃 | 国产午夜精品一区二区三区嫩草 | 色综合久久久久久久久久 | 特片网久久 | 精品国产一区二区三区久久久 | 午夜a狂野欧美一区二区 | 国产成人强伦免费视频网站 | 久久国产一二三 | 欧美人成在线 |