In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >
Share
Shulou(Shulou.com)06/02 Report--
This article mainly explains "how to use JWT for identity and permission verification in Spring Boot". Interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn "how Spring Boot uses JWT for identity and permission verification".
Controller
This UserControler is mainly used to verify that the permission configuration is in effect.
The getAllUser () method annotated @ PreAuthorize ("hasAnyRole ('ROLE_DEV','ROLE_PM')") means that the method can be accessed by the two roles DEV,PM, while the deleteUserById () annotated @ PreAuthorize ("hasAnyRole (' ROLE_ADMIN')") modifier represents that it can only be accessed by ADMIN.
/ * @ author shuang.kou * / @ RestController@RequestMapping ("/ api") public class UserController {private final UserService userService; private final CurrentUser currentUser; public UserController (UserService userService, CurrentUser currentUser) {this.userService = userService; this.currentUser = currentUser } @ GetMapping ("/ users") @ PreAuthorize ("hasAnyRole ('ROLE_DEV','ROLE_PM')") public ResponseEntity getAllUser (@ RequestParam (value = "pageNum", defaultValue = "0") int pageNum, @ RequestParam (value = "pageSize", defaultValue = "10") int pageSize) {System.out.println ("the current users accessing the interface are:" + currentUser.getCurrentUser (). ToString ()); Page allUser = userService.getAllUser (pageNum, pageSize) Return ResponseEntity.ok () .body (allUser);} @ DeleteMapping ("/ user") @ PreAuthorize ("hasAnyRole ('ROLE_ADMIN')") public ResponseEntity deleteUserById (@ RequestParam ("username") String username) {userService.deleteUserByUserName (username); return ResponseEntity.ok () .build ();}} Security Certification tool class
There are some common methods such as generating token and parsing token to get related information and so on.
/ * * @ author shuang.kou * / public class JwtTokenUtils {/ * generate enough secure random keys to fit the signature that conforms to the specification * / private static byte [] apiKeySecretBytes = DatatypeConverter.parseBase64Binary (SecurityConstants.JWT_SECRET_KEY); private static SecretKey secretKey = Keys.hmacShaKeyFor (apiKeySecretBytes); public static String createToken (String username, List roles, boolean isRememberMe) {long expiration = isRememberMe? SecurityConstants.EXPIRATION_REMEMBER: SecurityConstants.EXPIRATION String tokenPrefix = Jwts.builder () .setHeaderParam ("typ", SecurityConstants.TOKEN_TYPE) .signWith (secretKey, SignatureAlgorithm.HS256) .claim (SecurityConstants.ROLE_CLAIMS, String.join (",") Roles) .setIssuer ("SnailClimb") .setIssuedAt (new Date ()) .setSubject (username) .setExpiration (new Date (System.currentTimeMillis () + expiration * 1000)) .compact () Return SecurityConstants.TOKEN_PREFIX + tokenPrefix;} private boolean isTokenExpired (String token) {Date expiredDate = getTokenBody (token). GetExpiration (); return expiredDate.before (new Date ());} public static String getUsernameByToken (String token) {return getTokenBody (token). GetSubject () } / * get all user roles * / public static List getUserRolesByToken (String token) {String role = (String) getTokenBody (token) .get (SecurityConstants.ROLE_CLAIMS); return Arrays.stream (role.split (",")) .map (SimpleGrantedAuthority::new) .roles (Collectors.toList ()) } private static Claims getTokenBody (String token) {return Jwts.parser () .setSigningKey (secretKey) .parseClaimsJws (token) .getBody ();} filter
Let's first take a look at the two more important filters.
The first filter, which is mainly used for login authentication based on the user's username and password (which must have two parameters in the user's request), inherits UsernamePasswordAuthenticationFilter and overrides the following three methods:
AttemptAuthentication (): authenticates the user.
SuccessfulAuthentication (): the method that is called after successful user authentication.
UnsuccessfulAuthentication (): the method that is called when user authentication fails.
/ * * @ author shuang.kou * if the username and password are correct, the filter will create a JWT Token and return it in the header of HTTP Response in the format: token: "Bearer + specific token value" * / public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {private ThreadLocal rememberMe = new ThreadLocal (); private AuthenticationManager authenticationManager; public JWTAuthenticationFilter (AuthenticationManager authenticationManager) {this.authenticationManager = authenticationManager / / set the URL super.setFilterProcessesUrl (SecurityConstants.AUTH_LOGIN_URL) of the login request;} @ Override public Authentication attemptAuthentication (HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {ObjectMapper objectMapper = new ObjectMapper () Try {/ / get login information from the input stream LoginUser loginUser = objectMapper.readValue (request.getInputStream (), LoginUser.class); rememberMe.set (loginUser.getRememberMe ()) / / this part is the same as the source code in the attemptAuthentication method, / / only because the source code of this method is to make the user name and password dead, so we rewrite UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken (loginUser.getUsername (), loginUser.getPassword ()); return authenticationManager.authenticate (authRequest) } catch (IOException e) {e.printStackTrace (); return null }} / * * if the verification is successful Generate token and return * / @ Override protected void successfulAuthentication (HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) {JwtUser jwtUser = (JwtUser) authentication.getPrincipal () List roles = jwtUser.getAuthorities () .stream () .map (GrantedAuthority::getAuthority) .map (Collectors.toList ()); / / create Token String token = JwtTokenUtils.createToken (jwtUser.getUsername (), roles, rememberMe.get ()); / / return Token response.setHeader (SecurityConstants.TOKEN_HEADER, token) in Http Response Header @ Override protected void unsuccessfulAuthentication (HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException {response.sendError (HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage ());}}
This filter inherits BasicAuthenticationFilter and is mainly used to handle resources that can only be accessed after authentication. It checks the HTTP request for the existence of Authorization headers with the correct tokens and verifies the validity of the token.
The / * filter processes all HTTP requests and checks for the existence of Authorization headers with the correct tokens. For example, if the token does not expire or the signing key is correct. * * @ author shuang.kou * / public class JWTAuthorizationFilter extends BasicAuthenticationFilter {private static final Logger logger = Logger.getLogger (JWTAuthorizationFilter.class.getName ()); public JWTAuthorizationFilter (AuthenticationManager authenticationManager) {super (authenticationManager) } @ Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {String authorization = request.getHeader (SecurityConstants.TOKEN_HEADER) / / if there is no Authorization information in the request header, if (authorization = = null | |! authorization.startsWith (SecurityConstants.TOKEN_PREFIX)) {chain.doFilter (request, response) is released directly; return;} / / if there is token in the request header, it is parsed, and the authorization information SecurityContextHolder.getContext () .setAuthentication (getAuthentication (authorization)) is set. Super.doFilterInternal (request, response, chain);} / * here get user information from token and create a new token * / private UsernamePasswordAuthenticationToken getAuthentication (String authorization) {String token = authorization.replace (SecurityConstants.TOKEN_PREFIX, "); try {String username = JwtTokenUtils.getUsernameByToken (token) / / obtain the roles List userRolesByToken = JwtTokenUtils.getUserRolesByToken (token); if (! StringUtils.isEmpty (username)) {return new UsernamePasswordAuthenticationToken (username, null, userRolesByToken);}} catch (SignatureException | ExpiredJwtException exception) {logger.warning ("Request to parse JWT with invalid signature. Detail: "+ exception.getMessage ();} return null;}}
When a user logs in with the token information returned by the system, it first goes through the doFilterInternal () method, which fetches the token information from the requested Header, and then determines whether the token information is empty and whether the token information is in the correct format.
If there is a token in the request header and the format of the token is correct, the token is parsed and the validity of the token is determined, and then the authorization information SecurityContextHolder.getContext () .setAuthentication (getAuthentication (authorization)) is set globally in the Spring Security.
CurrentUser
We said when talking about the filter that when a successfully authenticated user accesses the system, its authentication information will be set in the Spring Security global. So, in that case, it's easy to get the authorization information for the currently logged-in user elsewhere, through the SecurityContextHolder.getContext (). GetAuthentication (); method. To do this, we implemented a class specifically designed to get the current user:
/ * @ author shuang.kou * get the currently requested user * / @ Componentpublic class CurrentUser {private final UserDetailsServiceImpl userDetailsService; public CurrentUser (UserDetailsServiceImpl userDetailsService) {this.userDetailsService = userDetailsService;} public JwtUser getCurrentUser () {return (JwtUser) userDetailsService.loadUserByUsername (getCurrentUserName ()) } / * TODO: due to the consistent failure of injecting UserDetailsServiceImpl into the class JWTAuthorizationFilter, * the user cannot be found correctly, so the Principal stored in Authentication is the name of the current user extracted from the token * / private static String getCurrentUserName () {Authentication authentication = SecurityContextHolder.getContext (). GetAuthentication () If (authentication! = null & & authentication.getPrincipal ()! = null) {return (String) authentication.getPrincipal ();} return null;}} exception dependent
JWTAccessDeniedHandler implements that AccessDeniedHandler is mainly used to resolve exceptions when authenticated users access resources that require permissions.
/ * * @ author shuang.kou * AccessDeineHandler is used to resolve exceptions when authenticated users access resources that require permissions * / public class JWTAccessDeniedHandler implements AccessDeniedHandler {/ * when users attempt to access REST resources that require permissions and lack of permissions * this method will be called to send 401 response and error message * / @ Override public void handle (HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {accessDeniedException = new AccessDeniedException ("Sorry you don not enough permissions to access it!") Response.sendError (HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage ();}}
JWTAuthenticationEntryPoint implements AuthenticationEntryPoint to resolve exceptions when anonymous users access resources that require permissions.
/ * * @ author shuang.kou * AuthenticationEntryPoint is used to resolve exceptions when anonymous users access resources that require permission * / public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {/ * when a user tries to access a REST resource that requires permission but does not provide Token or the Token expires * this method will be called to send 401 response and error message * / @ Override public void commence (HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {response.sendError (HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage ()) }} configuration class
In the SecurityConfig configuration class, we mainly configure:
Password encoder BCryptPasswordEncoder (passwords stored in the database need to be encrypted).
Set custom UserDetailsService and password encoders for AuthenticationManager
The Spring Security configuration specifies which paths for resources to be accessed by authenticated users, which are not needed, and which can only be accessed by specific roles.
Add the two filters we customized to the Spring Security configuration
Add two custom exception classes for handling permission authentication to the Spring Security configuration
@ EnableWebSecurity@EnableGlobalMethodSecurity (prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter {@ Autowired UserDetailsServiceImpl userDetailsServiceImpl; / * password Encoder * / @ Bean public BCryptPasswordEncoder bCryptPasswordEncoder () {return new BCryptPasswordEncoder ();} @ Bean public UserDetailsService createUserDetailsService () {return userDetailsServiceImpl } @ Override protected void configure (AuthenticationManagerBuilder auth) throws Exception {/ / set custom userDetailsService and password encoder auth.userDetailsService (userDetailsServiceImpl) .passwordEncoder (bCryptPasswordEncoder ()) } @ Override protected void configure (HttpSecurity http) throws Exception {http.cors () .and () / / disable CSRF .csrf () .disable () .authorizeRequests () .antMatrices (HttpMethod.POST "/ auth/login") .permitAll () / / Resources under the specified path require authenticated users to access .antMatrices ("/ api/**") .authenticated () .antMatrices (HttpMethod.DELETE) "/ api/**") .hasRole ("ADMIN") / / others have been released .anyRequest () .permitAll () .and () / add custom Filter .addFilter (new JWTAuthenticationFilter (authenticationManager () .addFilter (new JWTAuthorizationFilter (authenticationManager () / / session (no session creation) .sessionManagement () .sessionManagement () .sessionCreationPolicy (SessionCreationPolicy.STATELESS) .and () / authorization exception handling .authorizationHandling () .authenticationEntryPoint (new JWTAuthenticationEntryPoint ()) .acce ssDeniedHandler (new JWTAccessDeniedHandler ()) }}
Cross-domain:
The trick here is that if you don't set exposedHeaders ("Authorization") to expose the "Authorization" attribute in header to the client application, the front end will not get the token information.
@ Configurationpublic class CorsConfiguration implements WebMvcConfigurer {@ Override public void addCorsMappings (CorsRegistry registry) {registry.addMapping ("/ * *") .allowedOrigins ("*") / / expose other attributes in header to client applications / / if you don't set this attribute, the front end cannot get Authorization, that is, token, through response header. .exposedHeaders ("Authorization") .allowCredentials (true) .allowedMethods ("GET") "POST", "DELETE", "PUT") .maxAge (3600) Analysis of advantages and disadvantages of JWT
Stateless, the server does not need to store Session information.
Effectively avoid CSRF attacks.
Suitable for mobile applications.
Single sign-on is friendly.
Shortcoming
Token is still valid in scenarios such as logout and login.
Renewal of token
At this point, I believe you have a deeper understanding of "how Spring Boot uses JWT for identity and permission authentication". You might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.