From my previous two articles, YOU already know what JWT is, how to use it, and how to use it with Spring Security. So this section uses the code to realize the JWT login authentication and authentication process.
First, environmental preparation
- The Spring Boot project was established and integrated with Spring Security, and the project started normally
- Write an HTTP GET service interface via controller, such as “/hello”.
- It implements the most basic dynamic data verification and permission allocation, namely the UserDetailsService interface and UserDetails interface. Both of these interfaces provide validation information to Spring Security about users, roles, permissions, and so on
- If you’ve learned Spring Security’s formLogin login mode, remove the formLogin() configuration section from the HttpSecurity configuration altogether. Because JWT uses the JSON interface entirely, there is no FROM form submission.
- CSRF ().disable() must be added to the HttpSecurity configuration to temporarily disable XSS CSRF defense. This is not safe, which we will deal with in a later chapter.
We’ve covered all of this in previous articles. If you’re still not familiar with it, check out the previous article.
Second, develop the JWT tool class
Introduce the JWT toolkit JJWT via Maven coordinates
<dependency> <groupId> IO. Jsonwebtoken </groupId> <artifactId> JJWT </artifactId> <version>0.9.0</version> </dependency>Copy the code
Add the following custom configurations for JWT to application.yml
jwt:
header: JWTHeaderName
secret: aabbccdd
expiration: 3600000 Copy the code
- Where header is the name of the HTTP header that carries the JWT token. Although I’ll call it JWTHeaderName here, in actual production the less readable the better.
- Secret is the key used to encrypt and decrypt the underlying JWT information. Although I have written it to the configuration file here, it is usually not written directly to the configuration file in real production. Instead, it is passed through the application’s startup parameters and needs to be changed periodically.
- Expiration is the expiration time of the JWT token.
Write a Spring Boot configuration auto-loading utility class.
@data@configurationProperties (prefix = "JWT ") @Component public class JwtTokenUtil implements Serializable {private String Secret; private Long expiration; private String header; Public String generateToken(userDetails userDetails) {public String generateToken(userDetails userDetails) { Map<String, Object> claims = new HashMap<>(2); claims.put("sub", userDetails.getUsername()); claims.put("created", new Date()); return generateToken(claims); } /** * getUsernameFromToken ** @param token * @return username */ public String getUsernameFromToken(String token) {String username; try { Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) { username = null; } return username; } /** * Check whether token has expired ** @param token * @return Whether token has expired */ public Boolean isTokenExpired(String token) {try {Claims Claims = getClaimsFromToken(token); Date expiration = claims.getExpiration(); return expiration.before(new Date()); } catch (Exception e) { return false; }} public String refreshToken(String token) {String refreshedToken; try { Claims claims = getClaimsFromToken(token); claims.put("created", new Date()); refreshedToken = generateToken(claims); } catch (Exception e) { refreshedToken = null; } return refreshedToken; } public Boolean validateToken(String token, String token, String token, String token, String token, String token, String token, String token, String token) UserDetails userDetails) { SysUser user = (SysUser) userDetails; String username = getUsernameFromToken(token); return (username.equals(user.getUsername()) && ! isTokenExpired(token)); } /** * Generate tokens from claims ** @param Claims data declaration * @return token */ private String generateToken(Map<String, Object> claims) { Date expirationDate = new Date(System.currentTimeMillis() + expiration); return Jwts.builder().setClaims(claims) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } /** * private Claims getClaimsFromToken(String token) {private Claims getClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } catch (Exception e) { claims = null; } return claims; }}Copy the code
The above code is to develop a JWT token generation and refresh utility class using the methods provided by io.jsonWebToken.jjwt.
3. Develop login interface (interface to obtain Token)
- The “/authentication” interface is used for login authentication and generates the JWT back to the client
- The “/ refreshToken “interface is used to refresh the JWT and update the validity period of the JWT token
@RestController public class JwtAuthController { @Resource private JwtAuthService jwtAuthService; @PostMapping(value = "/authentication") public AjaxResponse login(@RequestBody Map<String, String> map) { String username = map.get("username"); String password = map.get("password"); if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { return AjaxResponse.error( new CustomException(customExceptionType. USER_INPUT_ERROR," User name and password cannot be empty "); } return AjaxResponse.success(jwtAuthService.login(username, password)); } @PostMapping(value = "/refreshtoken") public AjaxResponse refresh(@RequestHeader("${jwt.header}") String token) { return AjaxResponse.success(jwtAuthService.refreshToken(token)); }}Copy the code
The core token business logic is written in the JwtAuthService
- In the login method, the user name and password are used for login authentication. Throw a BadCredentialsException if validation fails. If the validation succeeds, the program proceeds to generate a JWT response to the front end
- The refreshToken method can only be refreshed if the JWT token has not expired, and then it cannot be refreshed. You need to log in again.
@Service public class JwtAuthService { @Resource private AuthenticationManager authenticationManager; @Resource private UserDetailsService userDetailsService; @Resource private JwtTokenUtil jwtTokenUtil; public String login(String username, String password) {/ / use the user name password to log in to verify UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password ); Authentication authentication = authenticationManager.authenticate(upToken); SecurityContextHolder.getContext().setAuthentication(authentication); / / generated JWT populated UserDetails populated UserDetails = userDetailsService. LoadUserByUsername (username); return jwtTokenUtil.generateToken(userDetails); } public String refreshToken(String oldToken) { if (! jwtTokenUtil.isTokenExpired(oldToken)) { return jwtTokenUtil.refreshToken(oldToken); } return null; }}Copy the code
For use by the AuthenticationManager, so in the inheritance WebSecurityConfigurerAdapter SpringSecurity configuration implementation class, will the AuthenticationManager statement for a Bean. And open access to “/authentication” and “/ refreshToken “. How to open access has been explained in our previous article.
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}Copy the code
Interface access authentication filter
When the user logs in for the first time, we return the JWT token to the client, which should save it. When making an interface request, the token belt is placed in the HTTP header with the same name as the jwt.header configuration so that the server can parse it. Let’s define an interceptor:
- Intercepts the interface request, obtains the token from the request request, and resolves the user name from the token
- Then the UserDetailsService obtains the system user (from the database, or another of its storage media)
- Based on the user information and the JWT token, verify the consistency of the system user with the user input and determine whether the JWT is expired. If not, this indicates that the user is indeed a user of the system.
- However, just because you’re a system user doesn’t mean you have access to all the interfaces. So you need to construct UsernamePasswordAuthenticationToken message users, permissions, and the information via the authentication to Spring Security. Spring Security will use this to determine your interface access rights.
@Slf4j @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Resource private MyUserDetailsService userDetailsService; @Resource private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {// Start with JWT token String authHeader = request.getheader (jwTTokenUtil.getheader ()); The info (" authHeader: {} ", authHeader); If (authHeader! = null && stringutils. isNotEmpty(authHeader) {// obtain the username based on the token String username = jwtTokenUtil.getUsernameFromToken(authHeader); if (username ! = null && SecurityContextHolder. GetContext () getAuthentication () = = null) {/ / through the user name for the user's information populated UserDetails populated UserDetails = this.userDetailsService.loadUserByUsername(username); / / verify whether JWT expired if (jwtTokenUtil validateToken (authHeader, populated userDetails)) {/ / loading information users, roles and permissions, Spring Security access according to these information judgment interface UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource() .buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); }}Copy the code
In spring Security configuration class (namely WebSecurityConfigurerAdapter implementation class of the configure (HTTP) HttpSecurity configuration method, add the following configuration:
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);Copy the code
- Since we use JWT, indicating that our application is a separated application, we can enable STATELESS to disable the use of sessions. Of course, this is not always the case, and there are ways in which sessions can be used for applications that are separated from the front and back ends, which is not the core of this article.
- Our custom jwtAuthenticationTokenFilter, loaded into the front of the UsernamePasswordAuthenticationFilter.
Five, test:
Test the login interface, that is, the interface to obtain the token. Enter the correct user name and password to obtain the token.
Let’s access a simple interface we defined, “/hello”, but pass no JWT token, so access is blocked. When we pass the token returned in the previous step to the header, we can normally respond to the interface result of Hello.
We look forward to your attention
- I recommend the blogger’s series of documents: “Hand on Hand to teach you to learn SpringBoot Series – Chapter 16, Section 97”.
- This article is reprinted with a credit (must be accompanied by a link, not only the text) : Antetokounmpo blog.