Security integration JWT part 09

Posted by flamtech on Thu, 28 Oct 2021 12:23:43 +0200

function

Integrate JWT into the project, which is divided into two stages
1. Log in for authentication for the first time. If the authentication is successful, a token will be returned
2. Subsequent requests carry a token for authorization authentication, that is, they need to be re authenticated before each authorization

1 github: Source address

2 security08 subproject

Introduce jwt dependency

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.yzm</groupId>
        <artifactId>security</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
    </parent>

    <artifactId>security08</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>security08</name>
    <description>Demo project for Spring Boot</description>

    <dependencies>
        <dependency>
            <groupId>com.yzm</groupId>
            <artifactId>common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!-- jwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Project structure

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test04?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
    username: root
    password: root

  main:
    allow-bean-definition-overriding: true

mybatis-plus:
  mapper-locations: classpath:/mapper/*Mapper.xml
  type-aliases-package: com.yzm.security08.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3 JwtUtils jwt tool class

package com.yzm.security08.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * JWT Tool class
 */
public class JwtUtils implements Serializable {

    private static final long serialVersionUID = 8527289053988618229L;
    /**
     * token head
     * token prefix
     */
    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Basic ";
    /**
     * User name
     */
    public static final String USERNAME = Claims.SUBJECT;
    public static final String PASSWORD = "password";
    /**
     * Permission list
     */
    public static final String AUTHORITIES = "authorities";
    /**
     * secret key
     */
    private static final String SECRET = "abcdefg";
    private static final String JWT_SECRET = "7786df7fc3a34e26a61c034d5ec8245d";
    /**
     * Expiration time 5 minutes
     * Refresh time 2 minutes
     */
    public static final long TOKEN_EXPIRED_TIME = 5 * 60 * 1000L;
    public static final long TOKEN_REFRESH_TIME = 2 * 60 * 1000L;

    public static String generateToken(Map<String, Object> claims) {
        return generateToken(claims, 0L);
    }

    /**
     * Generate token
     */
    public static String generateToken(Map<String, Object> claims, long expireTime) {
        if (expireTime <= 0L) expireTime = TOKEN_EXPIRED_TIME;

        Map<String, Object> headMap = new HashMap<>();
        headMap.put("typ", "JWT");
        headMap.put("alg", "HS256");

        return Jwts.builder()
                .setHeader(headMap)
                //The unique ID of the JWT, which can be set to a non duplicate value according to business needs, is mainly used as a one-time token to avoid replay attacks
                .setId(UUID.randomUUID().toString())
                //. setIssuer("issuer of this JWT, whether to use is optional")
                //. setSubject("the user for which the JWT is targeted, whether to use it is optional")
                //. setAudience("the party receiving the JWT, whether to use it is optional")
                //If there is a private declaration, you must first set the private declaration created by yourself. This is to assign a value to the builder's claim. Once it is written after the assignment of the standard declaration, it overwrites those standard declarations
                .setClaims(claims)
                //Issuing time (token generation time)
                .setIssuedAt(new Date())
                //Effective time (token is invalid before the specified time)
                .setNotBefore(new Date())
                //Expiration time (token is invalid after the specified time)
                .setExpiration(new Date(System.currentTimeMillis() + expireTime))
                //Set the signature algorithm and secret key used for signature
//                .signWith(SignatureAlgorithm.HS256, JWT_SECRET)
                .signWith(SignatureAlgorithm.HS256, generalKey())
                .compact();
    }

    /**
     * Authentication token
     */
    public static Claims verifyToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    //Signature key
//                    .setSigningKey(JWT_SECRET)
                    .setSigningKey(generalKey())
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException e) {
            // When the token expires, an exception is thrown directly, but the claims object can still be obtained
            claims = e.getClaims();
        }
        return claims;
    }

    /**
     * Generate encryption key from key
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.decodeBase64(SECRET);
//        return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return new SecretKeySpec(encodedKey, SignatureAlgorithm.HS256.getJcaName());
    }

    /**
     * Expired
     * true: be overdue
     * false: Not expired
     */
    public static boolean isExpired(String token) {
        Claims claims = verifyToken(token);
        //Compare with the current time to determine whether it expires
        return claims.getExpiration().before(new Date());
    }

    /**
     * Get user name from token
     */
    public static String getUsernameFromToken(String token) {
        Claims claims = verifyToken(token);
        return claims.getSubject();
    }

    /**
     * Get request token
     */
    public static String getTokenFromRequest(HttpServletRequest request) {
        String token = request.getHeader(TOKEN_HEADER);
        if (token == null) token = request.getHeader("token");
        if (StringUtils.isBlank(token))
            return null;
        return token.startsWith(TOKEN_PREFIX) ? token.substring(TOKEN_PREFIX.length()) : token;
    }
}

4. In the first stage, log in for authentication and obtain the token

The first login is still in the traditional form login mode, so we can directly use UsernamePasswordAuthenticationFilter to intercept / login for authentication processing. However, I have customized a JwtAuthenticationFilter, which has the same function as UsernamePasswordAuthenticationFilter
JwtAuthenticationFilter inherits UsernamePasswordAuthenticationFilter and overrides attemptAuthentication

package com.yzm.security08.config;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    public JwtAuthenticationFilter() {
        super();
    }

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = obtainUsername(request);
        String password = obtainPassword(request);
        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
            throw new AuthenticationServiceException("Wrong user name and password");
        }
		// Construct an unauthenticated JwtAuthenticationToken object
        JwtAuthenticationToken authToken = new JwtAuthenticationToken(username, password);
        this.setDetails(request, authToken);
        return this.getAuthenticationManager().authenticate(authToken);
    }

}

JwtAuthenticationToken inherits UsernamePasswordAuthenticationToken

package com.yzm.security08.config;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

public class JwtAuthenticationToken extends UsernamePasswordAuthenticationToken {

    public JwtAuthenticationToken(Object principal, Object credentials) {
        super(principal, credentials);
    }

    public JwtAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(principal, credentials, authorities);
    }
}

The Authentication Provider obtains the UserDetails object, verifies the password, and constructs the Authentication object for Authentication

package com.yzm.security08.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;

@Slf4j
public class JwtAuthenticationProvider implements AuthenticationProvider {

    private final UserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;

    public JwtAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return JwtAuthenticationToken.class.isAssignableFrom(authentication);
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        JwtAuthenticationToken authenticationToken = (JwtAuthenticationToken) authentication;
        String username = (String) authenticationToken.getPrincipal();
        String password = (String) authenticationToken.getCredentials();

        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        if (userDetails == null) throw new UsernameNotFoundException("Account exception");

        if (!passwordEncoder.matches(password, userDetails.getPassword())) {
            throw new BadCredentialsException("Voucher exception");
        }

        // Construct an authenticated JwtAuthenticationToken object
        JwtAuthenticationToken resultToken = new JwtAuthenticationToken(username, password,
                userDetails.getAuthorities());
        resultToken.setDetails(authentication.getDetails());
        return resultToken;
    }
}

After successful login authentication, a token is returned

package com.yzm.security08.config;

import com.yzm.common.utils.HttpUtils;
import com.yzm.security08.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class JwtAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        if (authentication instanceof JwtAuthenticationToken) {
            log.info("Login authentication");
            JwtAuthenticationToken authenticationToken = (JwtAuthenticationToken) authentication;
            String username = (String) authenticationToken.getPrincipal();

            Map<String, Object> map = new HashMap<>();
            map.put(JwtUtils.USERNAME, username);
            String token = JwtUtils.generateToken(map);
            HttpUtils.successWrite(response, token);
        }
    }
}

5 in the second stage, carry the token and authorize authentication

In the first stage above, if the token is obtained, the subsequent request header will carry the token access, so we need to define a Filter to intercept the request url with the Authorization parameter in the request header
JwtAuthorizationFilter is an authorization filter that mimics AbstractAuthenticationProcessingFilter

package com.yzm.security08.config;

import com.yzm.security08.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.*;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtAuthorizationFilter extends OncePerRequestFilter {

    // Intercept requests with Authorization in the header
    private final RequestMatcher authorizationRequestMatcher = new RequestHeaderRequestMatcher("Authorization");
    private final AuthenticationManager authenticationManager;

    private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

    public JwtAuthorizationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    protected boolean requiresAuthentication(HttpServletRequest request) {
        return authorizationRequestMatcher.matches(request);
    }

    @Override
    public void afterPropertiesSet() {
        Assert.notNull(authenticationManager, "authenticationManager must be specified");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        // Does the request header carry Authorization
        if (!requiresAuthentication(request)) {
            // The request header does not carry Authorization. It may be login or anonymous access. It is released directly and continues to the next filtering chain
            chain.doFilter(request, response);
            return;
        }

        Authentication authResult = null;
        AuthenticationException failed = null;
        try {
            String token = JwtUtils.getTokenFromRequest(request);
            if (StringUtils.isNotBlank(token)) {
            	// Construct an unauthenticated JwtAuthorizationToken. The authentication subject is a token and passed to the Provider
                JwtAuthorizationToken authorizationToken = new JwtAuthorizationToken(token, null);
                authorizationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                authResult = this.authenticationManager.authenticate(authorizationToken);
            } else {
                failed = new InsufficientAuthenticationException("JWT is Empty");
            }

        } catch (AuthenticationException e) {
            SecurityContextHolder.clearContext();
            failed = e;
        }

        if (authResult != null) {
            this.successfulAuthentication(request, response, authResult);
        } else {
            // Authentication failed
            this.unsuccessfulAuthentication(request, response, failed);
            return;
        }

        // Release and continue to the next filter chain
        chain.doFilter(request, response);
    }

    protected void unsuccessfulAuthentication(
            HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
            throws IOException, ServletException {
          // Authentication failed, clear
        SecurityContextHolder.clearContext();
        failureHandler.onAuthenticationFailure(request, response, failed);
    }

    protected void successfulAuthentication(
            HttpServletRequest request, HttpServletResponse response, Authentication authResult)
            throws IOException, ServletException {
         // Authentication succeeded. Save the authentication object to the context
        SecurityContextHolder.getContext().setAuthentication(authResult);
        successHandler.onAuthenticationSuccess(request, response, authResult);
    }

    public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) {
        Assert.notNull(successHandler, "successHandler cannot be null");
        this.successHandler = successHandler;
    }

    public void setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) {
        Assert.notNull(failureHandler, "failureHandler cannot be null");
        this.failureHandler = failureHandler;
    }

    protected AuthenticationSuccessHandler getSuccessHandler() {
        return this.successHandler;
    }

    protected AuthenticationFailureHandler getFailureHandler() {
        return this.failureHandler;
    }
}

JwtAuthorizationToken inherits UsernamePasswordAuthenticationToken

package com.yzm.security08.config;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

public class JwtAuthorizationToken extends UsernamePasswordAuthenticationToken {

    public JwtAuthorizationToken(Object principal, Object credentials) {
        super(principal, credentials);
    }

    public JwtAuthorizationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(principal, credentials, authorities);
    }
}

Similarly, if you need an authentication Provider, you can customize the implementation

package com.yzm.security08.config;

import com.yzm.security08.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.www.NonceExpiredException;

public class JwtAuthorizationProvider implements AuthenticationProvider {

    private final UserDetailsService userDetailsService;

    public JwtAuthorizationProvider(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return JwtAuthorizationToken.class.isAssignableFrom(authentication);
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        JwtAuthorizationToken authorizationToken = (JwtAuthorizationToken) authentication;
        String token = (String) authorizationToken.getPrincipal();
        if (JwtUtils.isExpired(token)) throw new NonceExpiredException("Token is Expired");

        Claims claims = JwtUtils.verifyToken(token);
        String username = claims.getSubject();
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        if (userDetails == null) throw new UsernameNotFoundException("Account exception");
		// Return Authentication object
        return new JwtAuthorizationToken(token, null, userDetails.getAuthorities());
    }
}

The authenticated Authentication object is returned to JwtAuthorizationFilter. After the JwtAuthorizationFilter is successfully called, it is processed and stored in the context
After the authentication is successful, the successful handler is called to check whether the token needs to be refreshed and returned to the response header.

package com.yzm.security08.config;

import com.yzm.security08.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class JwtAuthorizationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        if (authentication instanceof JwtAuthorizationToken) {
            log.info("Authorized authentication");
            JwtAuthorizationToken authorizationToken = (JwtAuthorizationToken) authentication;
            String token = (String) authorizationToken.getPrincipal();

            Claims claims = JwtUtils.verifyToken(token);
            long expiration = claims.getExpiration().getTime();
            // If the current time plus the refresh time is greater than the expiration time, the token will be refreshed
            if (System.currentTimeMillis() + JwtUtils.TOKEN_REFRESH_TIME >= expiration) {
                String username = claims.getSubject();
                Map<String, Object> map = new HashMap<>();
                map.put(JwtUtils.USERNAME, username);
                token = JwtUtils.generateToken(map);
            }

            response.setHeader("Authorization", token);
        }
    }
}

6 after the first and second phases are completed, it needs to be configured into the Security framework

package com.yzm.security08.config;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
 * Login authentication
 **/
public class JwtAuthenticationConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    private final UserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;

    public JwtAuthenticationConfigurer(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(http.getSharedObject(AuthenticationManager.class));
        jwtAuthenticationFilter.setAuthenticationSuccessHandler(new JwtAuthenticationSuccessHandler());
        jwtAuthenticationFilter.setAuthenticationFailureHandler(new JwtFailureHandler());

        JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider(userDetailsService, passwordEncoder);

        http
                .addFilterAt(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .authenticationProvider(jwtAuthenticationProvider);
    }
}
package com.yzm.security08.config;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.DefaultSecurityFilterChain;
/**
 * Authorized authentication
 **/
public class JwtAuthorizationConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    private final UserDetailsService userDetailsService;

    public JwtAuthorizationConfigurer(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        JwtAuthorizationFilter jwtAuthorizationFilter = new JwtAuthorizationFilter(http.getSharedObject(AuthenticationManager.class));
        jwtAuthorizationFilter.setAuthenticationSuccessHandler(new JwtAuthorizationSuccessHandler());
        jwtAuthorizationFilter.setAuthenticationFailureHandler(new JwtFailureHandler());

        JwtAuthorizationProvider jwtAuthorizationProvider = new JwtAuthorizationProvider(userDetailsService);

        http
                .addFilterAfter(jwtAuthorizationFilter, JwtAuthenticationFilter.class)
                .authenticationProvider(jwtAuthorizationProvider);
    }
}

Used in SecurityConfig global configuration
Now that we use jwt, we disable session
Nor is it a form login of the page. You use postman to request / login for login, so you release / login
Note to configure the user. The default UsernamePasswordAuthenticationFilter is not used, and the user-defined

package com.yzm.security08.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;


@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsService userDetailsService;

    public SecurityConfig(@Qualifier("jwtUserDetailsServiceImpl") UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    /**
     * Cipher encoder
     * passwordEncoder.encode It is used for encryption, and passwordEncoder.matches is used for decryption
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

//    /**
//     * Configure user
//     * Specify where to obtain the information of the authenticated user by default, that is, specify an implementation class of the UserDetailsService interface
//     */
//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        //Read the user from the database and decrypt it using the password encoder
//        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
//    }

    //Configure resource permission rules
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable() // Turn off CSRF
                .sessionManagement().disable() //Disable session
                .formLogin().disable() //Disable form login

                // Add token authentication
                .apply(new JwtAuthenticationConfigurer(userDetailsService, passwordEncoder()))
                .and()
                // Add token authorization
                .apply(new JwtAuthorizationConfigurer(userDetailsService))
                .and()

                .exceptionHandling()
                .accessDeniedHandler(new JwtAccessDeniedHandler())
                .and()

                // Log out
                .logout()
                .permitAll()
                .and()

                // Authorization policy of access path URL, such as registration, login free authentication, etc
                .authorizeRequests()
                .antMatchers("/", "/home", "/register", "/login").permitAll() //Specify url release
                .anyRequest().authenticated() //Any other request requires authentication
        ;
    }
}

The same processing program is used for authentication failures in the first and second stages

package com.yzm.security08.config;

import com.yzm.common.utils.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
public class JwtFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        log.info("Login failed:" + exception.getMessage());
        String msg = "Login failed";
        if (exception instanceof BadCredentialsException || exception instanceof UsernameNotFoundException) {
            msg = "Wrong user name or password";
        } else if (exception instanceof LockedException) {
            msg = "Account locked";
        } else if (exception instanceof AccountExpiredException) {
            msg = "Account expired";
        } else if (exception instanceof CredentialsExpiredException) {
            msg = "Voucher expiration";
        } else if (exception instanceof DisabledException) {
            msg = "Account disabled";
        }
        HttpUtils.errorWrite(response, msg);
    }
}

No access to handler

package com.yzm.security08.config;

import com.yzm.common.utils.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Exception when an authenticated user accesses an unauthorized resource
 */
@Slf4j
public class JwtAccessDeniedHandler extends AccessDeniedHandlerImpl {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        log.info("Authorization failed:" + accessDeniedException.getMessage());
        HttpUtils.errorWrite(response, accessDeniedException.getMessage());
    }

}

Here, the basic configuration is completed. Let's test it

7 test

Log in for the first time, authenticate and obtain a token

Carry a token to access

Topics: Spring Boot security