最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringSecurity構建基于JWT的登錄認證實現(xiàn)

 更新時間:2021年02月07日 09:26:44   作者:locotor在掘金  
這篇文章主要介紹了SpringSecurity構建基于JWT的登錄認證實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

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

一言以蔽之,JWT 可以攜帶非敏感信息,并具有不可篡改性??梢酝ㄟ^驗證是否被篡改,以及讀取信息內容,完成網(wǎng)絡認證的三個問題:“你是誰”、“你有哪些權限”、“是不是冒充的”。

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

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

目標功能點

通過填寫用戶名和密碼登錄。

  • 驗證成功后, 服務端生成 JWT 認證 token, 并返回給客戶端。
  • 驗證失敗后返回錯誤信息。
  • 客戶端在每次請求中攜帶 JWT 來訪問權限內的接口。

每次請求驗證 token 有效性和權限,在無有效 token 時拋出 401 未授權錯誤。
當發(fā)現(xiàn)請求帶著的 token 有效期快到了的時候,返回特定狀態(tài)碼,重新請求一個新 token。

準備工作

引入 Maven 依賴

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

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
 <groupId>com.fasterxml.jackson.core</groupId>
 <artifactId>jackson-core</artifactId>
 <version>2.12.1</version>
</dependency>
<dependency>
 <groupId>com.auth0</groupId>
 <artifactId>java-jwt</artifactId>
 <version>3.12.1</version>
</dependency>

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

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

User

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

@Data
public class User implements UserDetails {

 private static final long serialVersionUID = 1L;

 private String username;

 private String password;

 private Collection<? extends GrantedAuthority> authorities;

 ...
}

UserService

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

@Service
public class UserService implements UserDetailsService {
 
 @Autowired
 UserMapper userMapper;

 @Override
 @Transactional
 public User loadUserByUsername(String username) {
  return userMapper.getByUsername(username);
 }

 ...
}

創(chuàng)建 JWT 工具類

這個工具類主要負責 token 的生成,驗證,從中取值。

@Component
public class JwtTokenProvider {

 private static final long JWT_EXPIRATION = 5 * 60 * 1000L; // 五分鐘過期

 public static final String TOKEN_PREFIX = "Bearer "; // token 的開頭字符串

 private String jwtSecret = "XXX 密鑰,打死也不能告訴別人";

 ...
}

生成 JWT:從以通過驗證的認證對象中,獲取用戶信息,然后用指定加密方式,以及過期時間生成 token。這里簡單的只加了用戶名這一個信息到 token 中:

public String generateToken(Authentication authentication) {
 User userPrincipal = (User) authentication.getPrincipal(); // 獲取用戶對象
 Date expireDate = new Date(System.currentTimeMillis() + JWT_EXPIRATION); // 設置過期時間
 try {
  Algorithm algorithm = Algorithm.HMAC256(jwtSecret); // 指定加密方式
  return JWT.create().withExpiresAt(expireDate).withClaim("username", userPrincipal.getUsername()) 
    .sign(algorithm); // 簽發(fā) JWT
 } catch (JWTCreationException jwtCreationException) {
  return null;
 }
}

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

public boolean validateToken(String authToken) {
 try {
  Algorithm algorithm = Algorithm.HMAC256(jwtSecret); // 和簽發(fā)保持一致
  JWTVerifier verifier = JWT.require(algorithm).build();
  verifier.verify(authToken);
  return true;
 } catch (JWTVerificationException jwtVerificationException) {
  return false;
 }
}

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

public String getUsernameFromJWT(String authToken) {
 try {
  DecodedJWT jwt = JWT.decode(authToken);
  return jwt.getClaim("username").asString();
 } catch (JWTDecodeException jwtDecodeException) {
  return null;
 }
}

登錄

登錄部分需要創(chuàng)建三個文件:負責登錄接口處理的攔截器,登陸成功或者失敗的處理類。

LoginFilter

Spring Security 默認自帶表單登錄,負責處理這個登錄驗證過程的過濾器叫“UsernamePasswordAuthenticationFilter”,不過它只支持表單傳值,這里用自定義的類繼承它,使其能夠支持 JSON 傳值,負責登錄驗證接口。
這個攔截器只需要負責從請求中取值即可,驗證工作 Spring Security 會幫我們處理好。

public class LoginFilter extends UsernamePasswordAuthenticationFilter {

 @Override
 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
  if (!request.getMethod().equals("POST")) {
   throw new AuthenticationServiceException("登錄接口方法不支持: " + request.getMethod());
  }
  if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
   Map<String, String> loginData = new HashMap<>();
   try {
    loginData = new ObjectMapper().readValue(request.getInputStream(), Map.class);
   } catch (IOException e) {
   }
   String username = loginData.get(getUsernameParameter());
   String password = loginData.get(getPasswordParameter());
   if (username == null) {
    username = "";
   }
   if (password == null) {
    password = "";
   }
   username = username.trim();
   UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,
     password);
   setDetails(request, authRequest);
   return this.getAuthenticationManager().authenticate(authRequest);
  } else {
   return super.attemptAuthentication(request, response);
  }
 }

}

LoginSuccessHandler

負責在登錄成功后,生成 JWT 給前端。

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

 @Autowired
 private JwtTokenProvider jwtTokenProvider;

 @Override
 public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
   Authentication authentication) throws IOException, ServletException {

  ResponseData responseData = new ResponseData();
  String token = jwtTokenProvider.generateToken(authentication);
  responseData.setData(JwtTokenProvider.TOKEN_PREFIX + token);
  response.setContentType("application/json;charset=utf-8");
  ObjectMapper mapper = new ObjectMapper();
  mapper.writeValue(response.getWriter(), responseData);
 }

}

LoginFailureHandler

驗證失敗后,返回錯誤信息。

@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {

 @Override
 public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
   AuthenticationException exception) throws IOException, ServletException {
  response.setContentType("application/json;charset=utf-8");
  ResponseData respBean = setResponseData(exception);
  ObjectMapper mapper = new ObjectMapper();
  mapper.writeValue(response.getWriter(), respBean);
 }

 private ResponseData setResponseData(AuthenticationException exception) {
  if (exception instanceof LockedException) {
   return ResponseData.build("用戶已被鎖定");
  } else if (exception instanceof CredentialsExpiredException) {
   return ResponseData.build("密碼已過期");
  } else if (exception instanceof AccountExpiredException) {
   return ResponseData.build("用戶名已過期");
  } else if (exception instanceof DisabledException) {
   return ResponseData.build("賬戶不可用");
  } else if (exception instanceof BadCredentialsException) {
   return ResponseData.build("驗證失敗");
  }
  return ResponseData.build("登錄失敗,請聯(lián)系管理員");
 }

}

驗證

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

JwtAuthenticationFilter

負責在每次請求中,解析請求頭中的 JWT,從中取得用戶信息,生成驗證對象傳遞給下一個過濾器。

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

 @Autowired
 private JwtTokenProvider jwtProvider;

 @Override
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
   throws ServletException, IOException {
  try {
   String jwt = getJwtFromRequest(request);
   UsernamePasswordAuthenticationToken authentication = verifyToken(jwt);
   if (authentication != null) {
    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
   }
   SecurityContextHolder.getContext().setAuthentication(authentication);
  } catch (Exception e) {
   logger.error("無法給 Security 上下文設置用戶驗證對象", e);
  }

  filterChain.doFilter(request, response);
 }

 private String getJwtFromRequest(HttpServletRequest request) {
  String bearerToken = request.getHeader("Authorization");
  if (bearerToken == null || !bearerToken.startsWith(JwtTokenProvider.TOKEN_PREFIX)) {
   logger.info("請求頭不含 JWT token,調用下個過濾器");
   return null;
  }

  return bearerToken.split(" ")[1].trim();
 }

 // 驗證token,并生成認證后的token
 private UsernamePasswordAuthenticationToken verifyToken(String token) {
  if (token == null) {
   return null;
  }
  // 認證失敗,返回null
  if (!jwtProvider.validateToken(token)) {
   return null;
  }
  // 提取用戶名
  String username = jwtProvider.getUsernameFromJWT(token);
  UserDetails userDetails = new User(username);

  // 構建認證過的token
  return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
 }
}

AuthenticationEntryPoint

這個類就比較簡單,只是在驗證不通過后,返回 401 響應,并記錄錯誤信息。

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

 private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
 
 @Override
 public void commence(HttpServletRequest request, HttpServletResponse response,
   AuthenticationException authException) throws IOException, ServletException {
  logger.error("驗證為通過. 提示信息 - {}", authException.getMessage());
  response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
 }

}

集中配置

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

@Configuration
@EnableWebSecurity
public class KanpmSecurityConfig extends WebSecurityConfigurerAdapter {

 @Autowired
 UserDetailsService userDetailsService;

 @Autowired
 private JwtAuthenticationEntryPoint unauthorizedHandler;

 @Autowired
 private JwtAuthenticationFilter jwtAuthenticationFilter;

 @Bean
 public LoginFilter loginFilter(LoginSuccessHandler loginSuccessHandler, LoginFailureHandler loginFailureHandler)
   throws Exception {
  LoginFilter loginFilter = new LoginFilter();
  loginFilter.setAuthenticationSuccessHandler(loginSuccessHandler);
  loginFilter.setAuthenticationFailureHandler(loginFailureHandler);
  loginFilter.setAuthenticationManager(authenticationManagerBean());
  loginFilter.setFilterProcessesUrl("/auth/login");
  return loginFilter;
 }

 @Bean
 @Override
 public AuthenticationManager authenticationManagerBean() throws Exception {
  return super.authenticationManagerBean();
 }

 @Bean
 public PasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder();
 }

 ...
}

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

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

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

@Override
protected void configure(HttpSecurity http) throws Exception {
 http.csrf().disable().anyRequest().authenticated().and()
   .exceptionHandling().authenticationEntryPoint(unauthorizedHandler);

 http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
 .addFilterAt(loginFilter(new LoginSuccessHandler(), new LoginFailureHandler()),
   UsernamePasswordAuthenticationFilter.class);
}

到此這篇關于SpringSecurity構建基于JWT的登錄認證實現(xiàn)的文章就介紹到這了,更多相關SpringSecurity JWT登錄認證內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Security6.4.2?自定義異常中統(tǒng)一響應遇到的問題

    Security6.4.2?自定義異常中統(tǒng)一響應遇到的問題

    本文主要介紹了Security6.4.2?自定義異常中統(tǒng)一響應遇到的問題,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2025-03-03
  • Java使用Tessdata做OCR圖片文字識別的詳細思路

    Java使用Tessdata做OCR圖片文字識別的詳細思路

    這篇文章主要介紹了Java使用Tessdata做OCR圖片文字識別的詳細思路,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-07-07
  • Java 反射修改類的常量值、靜態(tài)變量值、屬性值實例詳解

    Java 反射修改類的常量值、靜態(tài)變量值、屬性值實例詳解

    在本篇文章里小編給大家整理的是一篇關于Java 反射修改類的常量值、靜態(tài)變量值、屬性值實例詳解內容,有興趣的讀者們可以跟著學習下。
    2021-01-01
  • java Jersey框架初體驗

    java Jersey框架初體驗

    本篇主要是Jersey體驗,你將在不做任何編碼的情況下,體驗Jersey框架的神氣魅力!本文還假定你在eclipse里安裝了Maven插件
    2016-07-07
  • Intellij IDEA 2019 最新亂碼問題及解決必殺技(必看篇)

    Intellij IDEA 2019 最新亂碼問題及解決必殺技(必看篇)

    大家在使用Intellij IDEA 的時候會經(jīng)常遇到各種亂碼問題,今天小編給大家分享一些關于Intellij IDEA 2019 最新亂碼問題及解決必殺技,感興趣的朋友跟隨小編一起看看吧
    2020-04-04
  • Java中進程與線程的區(qū)別

    Java中進程與線程的區(qū)別

    這篇文章主要介紹了Java進程與線程的區(qū)別,進程(Process)是操作系統(tǒng)分配資源的基本單位,線程(Thread)是操作系統(tǒng)能夠進行運算調度的基本單位,下文更多兩者區(qū)別。需要的小伙伴可以參考一下
    2022-05-05
  • java轉發(fā)和重定向的區(qū)別

    java轉發(fā)和重定向的區(qū)別

    這篇文章主要介紹了java轉發(fā)和重定向的區(qū)別,需要的朋友可以參考下
    2014-10-10
  • struts2入門介紹及代碼實例

    struts2入門介紹及代碼實例

    這篇文章主要介紹了struts2入門介紹及代碼實例,具有一定借鑒價值,需要的朋友可以參考下。
    2017-12-12
  • Spring?Boot?Security認證之Redis緩存用戶信息詳解

    Spring?Boot?Security認證之Redis緩存用戶信息詳解

    本文介紹了如何使用Spring Boot Security進行認證,并通過Redis緩存用戶信息以提高系統(tǒng)性能,通過配置RedisUserDetailsManager,我們成功地將用戶信息存儲到了Redis中,并在Spring Security中進行了集成,需要的朋友可以參考下
    2024-01-01
  • 深入淺析Mybatis的缺陷問題

    深入淺析Mybatis的缺陷問題

    Mybatis是業(yè)界非常流行的持久層框架,輕量級、易用,在金融IT領域完全是領軍地位,比Hibernate更受歡迎,優(yōu)勢非常多,也是非常值得我們學習的。這篇文章主要介紹了Mybatis的缺陷問題的相關資料,需要的朋友可以參考下
    2016-10-10

最新評論

浦江县| 本溪市| 乡城县| 杭州市| 漾濞| 丹凤县| 东宁县| 郧西县| 县级市| 中卫市| 教育| 定兴县| 岑巩县| 东方市| 望江县| 牡丹江市| 双鸭山市| 北票市| 班戈县| 上犹县| 东安县| 山东省| 博乐市| 澄江县| 邯郸市| 当阳市| 信阳市| 保定市| 个旧市| 巴里| 泰来县| 太原市| 中方县| 商南县| 海淀区| 涪陵区| 诸暨市| 白朗县| 吴忠市| 洛隆县| 寿光市|