-->
侧边栏壁纸
博主头像
断钩鱼 博主等级

行动起来,活在当下

  • 累计撰写 28 篇文章
  • 累计创建 34 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

SpringSecurity 自定义认证+JWT 双令牌

halt
2021-10-22 / 0 评论 / 1 点赞 / 1693 阅读 / 0 字

介绍之类的不在多说了直奔主题

一、添加依赖

security + auth0 依赖

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.13.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

二、配置Security

1. 用户实体

package top.mengshuo.token.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * 用户实体
 *
 * @author mengshuo
 * @since 2021-10-21
 */
@Data
@Accessors(chain = true)
public class SysUser implements Serializable {
    private static final long serialVersionUID = -6027806415373867286L;

    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private String id;
    private String username;
    private String password;

}

2. Jwt工具类

jwt令牌的相关配置应该进行抽离 可以抽离到配置文件 也可以抽到数据库等位置

  • jwt工具类
package top.mengshuo.core.utils;


import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import top.mengshuo.core.entity.Tokens;
import top.mengshuo.core.enums.HttpResultCode;
import top.mengshuo.core.exception.SystemException;
import top.mengshuo.token.entity.SysUser;

import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

/**
 * Jwt 令牌工具类
 *
 * @author mengshuo
 * @since 2021-10-21
 */
@Log4j2
@Component
public class JwtTokenUtil {
    public static final String USER_NAME = "username";
    public static final String POWERS = "powers";
    
    public static final String ACCESS_TOKEN_SECRET = "qwertyuiop123456789";
    public static final String REFRESH_TOKEN_SECRET = "asdfghjkl123456789";
    public static final int ACCESS_TOKEN_EXPIRED_TIME = 60 * 50;
    public static final int REFRESH_TOKEN_EXPIRED_TIME = 60 * 60;

    private static ObjectMapper objectMapper;

    @Autowired
    public void setObjectMapper(ObjectMapper objectMapper) {
        JwtTokenUtil.objectMapper = objectMapper;
    }


    /**
     * 生成token
     *
     * @param user   用户信息
     * @param powers 用户权限信息
     * @return res
     */
    public static Tokens generatorToken(SysUser user, Collection<? extends GrantedAuthority> powers) {
        Calendar calendar = Calendar.getInstance();
        Date now = calendar.getTime();
        calendar.add(Calendar.SECOND, ACCESS_TOKEN_EXPIRED_TIME);
        try {
            String accessToken = JWT.create()
                    .withClaim(JwtTokenUtil.USER_NAME, user.getUsername())
                    .withClaim(JwtTokenUtil.POWERS, objectMapper.writeValueAsString(powers))
                    .withIssuedAt(now)
                    .withExpiresAt(calendar.getTime())
                    .sign(Algorithm.HMAC256(ACCESS_TOKEN_SECRET));
            return new Tokens().setAccessToken(accessToken).setAccessTokenExpiredTime(calendar.getTime());
        } catch (JsonProcessingException e) {
            log.error("》》》》》》 jwtToken 创建失败 原因为权限信息序列化时出错 《《《《《《");
            throw new SystemException(HttpResultCode.FAILED);
        }
    }

    /**
     * 生成双令牌
     *
     * @param user   用户信息
     * @param powers 用户权限信息
     * @return res
     */
    public static Tokens generatorDoubleToken(SysUser user, Collection<? extends GrantedAuthority> powers) {
        Tokens tokens = JwtTokenUtil.generatorToken(user, powers);
        Calendar calendar = Calendar.getInstance();
        Date now = calendar.getTime();
        calendar.add(Calendar.SECOND,REFRESH_TOKEN_EXPIRED_TIME);
        String refreshToken = JWT.create()
                .withClaim(JwtTokenUtil.USER_NAME, user.getUsername())
                .withIssuedAt(now)
                .withExpiresAt(calendar.getTime())
                .sign(Algorithm.HMAC256(REFRESH_TOKEN_SECRET));
        return tokens.setRefreshToken(refreshToken).setRefreshTokenExpiredTime(calendar.getTime());
    }

    /**
     * 解析access token
     *
     * @param token token
     * @return res
     */
    public static Map<String, Claim> decodeAccessToken(String token) {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(ACCESS_TOKEN_SECRET)).build();
        Map<String, Claim> res;
        try {
            res = verifier.verify(token).getClaims();
        } catch (Exception e) {
            return null;
        }

        return res;
    }

    /**
     * 解析refresh token
     *
     * @param token token
     * @return res
     */
    public static Map<String, Claim> decodeRefreshToken(String token) {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(REFRESH_TOKEN_SECRET)).build();
        Map<String, Claim> res;
        try {
            res = verifier.verify(token).getClaims();
        } catch (Exception e) {
            return null;
        }
        return res;
    }

    /**
     * 校验token
     *
     * @param token token
     * @return res
     */
    public static Boolean verifyToken(String token) {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(ACCESS_TOKEN_SECRET)).build();
        try {
            verifier.verify(token).getClaims();
        } catch (Exception e) {
            return false;
        }
        return true;
    }


}

  • Tokens
package top.mengshuo.core.entity;

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

/**
 * 令牌实体
 *
 * @author mengshuo
 * @since 2021-10-21
 */
@Accessors(chain = true)
@Data
public class Tokens implements Serializable {
    private static final long serialVersionUID = 867984083891301159L;


    private String accessToken;

    private Date accessTokenExpiredTime;

    private String refreshToken;

    private Date refreshTokenExpiredTime;
}

3. 添加异常处理

SpringSecurity对应的异常也有对应的处理器去处理 如果不去继承指定的处理器去处理 默认抛出异常

  • 统一异常处理

package top.mengshuo.core.exception;

import lombok.extern.log4j.Log4j2;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import top.mengshuo.core.entity.HttpResult;
import top.mengshuo.core.enums.HttpResultCode;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;

/**
 * 异常处理类
 *
 * @author mengshuo
 * @since 2021-10-21
 */
@Log4j2
@RestControllerAdvice
public class ExceptionAdvice {

    @ExceptionHandler(Exception.class)
    public HttpResult<?> exception(Exception e) {
        log.error("》》》》》》 系统异常默认 《《《《《《");
        log.error(e);
        return HttpResult.failed("服务器开小差啦....");
    }

    @ExceptionHandler(SystemException.class)
    public HttpResult<?> exception(SystemException e) {
        log.error("》》》》》》 系统自定义异常 《《《《《《");
        log.error(e);
        return HttpResult.failed(e.getMessage());
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public HttpResult<?> exception(HttpRequestMethodNotSupportedException e) {
        log.error("》》》》》》 请求方式不正确 《《《《《《");
        return HttpResult.failed("请求方式不正确");
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public HttpResult<?> exception(NoHandlerFoundException e) {
        log.error("》》》》》》 404 请求资源未找到 《《《《《《");
        return HttpResult.createResult(HttpResultCode.NOT_FOUND);
    }


    @ExceptionHandler({MethodArgumentNotValidException.class})
    public HttpResult<?> serviceException(MethodArgumentNotValidException e) {
        log.info("》》》》》》 参数不合法 《《《《《《");
        StringBuilder message = new StringBuilder("请检查参数,错误如下: ");
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        for (ObjectError error : allErrors) {
            message.append(error.getDefaultMessage()).append(";");
        }
        return HttpResult.failed(message.toString());
    }


    @ExceptionHandler({BindException.class})
    public HttpResult<?> serviceException(BindException e) {
        log.info("》》》》》》 参数不合法 《《《《《《");
        StringBuilder message = new StringBuilder("请检查参数,错误如下: ");
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        for (ObjectError error : allErrors) {
            message.append(error.getDefaultMessage()).append(";");
        }
        return HttpResult.failed(message.toString());
    }

    @ExceptionHandler({ConstraintViolationException.class})
    public HttpResult<?> serviceException(ConstraintViolationException e) {
        log.info("》》》》》》 参数不合法 《《《《《《");
        StringBuilder message = new StringBuilder("请检查参数,错误如下: ");
        Set<ConstraintViolation<?>> allErrors = e.getConstraintViolations();
        for (ConstraintViolation<?> error : allErrors) {
            message.append(error.getMessageTemplate()).append(";");
        }
        return HttpResult.failed(message.toString());
    }

    /**
     * 没有权限 security
     *
     * @param e 异常
     * @return 异常处理结果
     */
    @ExceptionHandler(AccessDeniedException.class)
    public HttpResult<?> serviceException(AccessDeniedException e) {
        log.info("》》》》》》 未经授权的访问 《《《《《《");
        return HttpResult.createResult(HttpResultCode.NOT_HAS_POWER);
    }

    /**
     * 账号处于冻结期 security
     *
     * @param e 异常
     * @return 异常处理结果
     */
    @ExceptionHandler(LockedException.class)
    public HttpResult<?> serviceException(LockedException e) {
        log.info("》》》》》》 账号处于冻结期 《《《《《《");
        return HttpResult.createResult(HttpResultCode.ACCOUNT_IS_LOCK);
    }

    /**
     * 用户名或密码错误 security
     *
     * @param e 异常
     * @return 异常处理结果
     */
    @ExceptionHandler({BadCredentialsException.class, UsernameNotFoundException.class})
    public HttpResult<?> serviceException(Exception e) {
        log.info("》》》》》》 用户名或者密码有误 《《《《《《");
        return HttpResult.createResult(HttpResultCode.ACCOUNT_PASS_ERROR);
    }

    /**
     * security认证异常处理器
     *
     * @param e 异常
     * @return 异常处理结果
     */
    @ExceptionHandler(AuthenticationServiceException.class)
    public HttpResult<?> exception(AuthenticationServiceException e) {
        log.info("》》》》》》 认证异常 《《《《《《");
        log.error(e.getMessage(), e);
        return HttpResult.createResult(HttpResultCode.ACCOUNT_PASS_ERROR);
    }

    /**
     * 内部认证异常处理器 一般是自定义认证过程中抛出的异常
     *
     * @param e 异常
     * @return 异常处理结果
     */
    @ExceptionHandler({InternalAuthenticationServiceException.class})
    public HttpResult<?> exception(InternalAuthenticationServiceException e) {
        log.info("》》》》》》 内部认证异常 《《《《《《");
        log.error(e.getMessage(), e);
        return HttpResult.createResult(HttpResultCode.ACCOUNT_NOT_ACTIVATE.getCode(), e.getMessage());
    }


}

  • 未登录异常处理
package top.mengshuo.core.exception;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import top.mengshuo.core.entity.HttpResult;
import top.mengshuo.core.enums.HttpResultCode;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 未经认证的访问
 *
 * @author mengshuo
 * @since 2021-10-21
 */
@Component
public class SecurityNotLoginExceptionHandler implements AuthenticationEntryPoint {
    @Resource
    private ObjectMapper objectMapper;

    /**
     * Commences an authentication scheme.
     * <p>
     * <code>ExceptionTranslationFilter</code> will populate the <code>HttpSession</code>
     * attribute named
     * <code>AbstractAuthenticationProcessingFilter.SPRING_SECURITY_SAVED_REQUEST_KEY</code>
     * with the requested target URL before calling this method.
     * <p>
     * Implementations should modify the headers on the <code>ServletResponse</code> as
     * necessary to commence the authentication process.
     *
     * @param request       that resulted in an <code>AuthenticationException</code>
     * @param response      so that the user agent can begin authentication
     * @param authException that caused the invocation
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(objectMapper.writer().writeValueAsString(HttpResult.createResult(HttpResultCode.NOT_LOGIN)));
        response.getWriter().flush();
    }
}

  • 自定义异常
package top.mengshuo.core.exception;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import top.mengshuo.core.enums.HttpResultCode;

/**
 * 系统自定义异常
 *
 * @author mengshuo
 * @since 2021-10-21
 */
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class SystemException extends RuntimeException {
    private static final long serialVersionUID = -4743049562216876178L;
    private Integer code;
    private String message;


    public SystemException(HttpResultCode code) {
        this.code = code.getCode();
        this.message = code.getMessage();
    }
}

  • 统一返回值
package top.mengshuo.core.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import top.mengshuo.core.enums.HttpResultCode;

import java.io.Serializable;

/**
 * 通用返回实体
 *
 * @author mengshuo
 * @since 2021-10-21
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class HttpResult<T> implements Serializable {
    private static final long serialVersionUID = 6471488393697889199L;
    private Integer code;
    private String message;
    private T data;

    public static HttpResult<?> success() {
        return createResult(HttpResultCode.SUCCESS);
    }

    public static HttpResult<?> success(String message) {
        return createResult(HttpResultCode.SUCCESS.getCode(), message);
    }

    public static <T> HttpResult<T> success(String message, T data) {
        return createResult(HttpResultCode.SUCCESS.getCode(), message, data);
    }

    public static HttpResult<?> failed() {
        return createResult(HttpResultCode.FAILED);
    }

    public static HttpResult<?> failed(String message) {
        return createResult(HttpResultCode.FAILED.getCode(), message);
    }


    public static HttpResult<?> createResult(HttpResultCode codeEnums) {
        return new HttpResult<>()
                .setCode(codeEnums.getCode())
                .setMessage(codeEnums.getMessage());
    }

    public static HttpResult<?> createResult(Integer code, String message) {
        return new HttpResult<>()
                .setCode(code)
                .setMessage(message);
    }

    public static <T> HttpResult<T> createResult(Integer code, String message, T data) {
        return new HttpResult<T>()
                .setCode(code)
                .setMessage(message)
                .setData(data);
    }

    public static <T> HttpResult<T> createResult(HttpResultCode codeEnums, T data) {
        return new HttpResult<T>()
                .setCode(codeEnums.getCode())
                .setMessage(codeEnums.getMessage())
                .setData(data);
    }


}

  • 状态码
package top.mengshuo.core.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * http 状态码
 *
 * @author mengshuo
 * @since 2021-10-21
 */
@Getter
@AllArgsConstructor
public enum HttpResultCode {
    /**
     * 请求成功
     */
    SUCCESS(20000, "请求成功"),
    /***
     * 未找到处理器
     */
    NOT_FOUND(40004, "请求资源未找到"),
    /**
     * 请求失败
     */
    FAILED(50000, "服务器开小差啦..."),
    /**
     * 系统拒绝执行
     */
    REFUSE(50001, "系统拒绝执行"),
    /**
     * 登录失败
     */
    LOGIN_ERROR(30001, "登录失败 账号密码错误"),
    /**
     * 用户未登录
     */
    NOT_LOGIN(30002, "用户未登录"),
    /**
     * 用户没有权限
     */
    NOT_HAS_POWER(30003, "抱歉您没有该权限"),
    /**
     * 账号被冻结
     */
    ACCOUNT_IS_LOCK(30004, "账号被冻结"),
    /**
     * 用户名或者密码有误
     */
    ACCOUNT_PASS_ERROR(30005, "用户名或者密码有误"),
    /**
     * 账号未激活
     */
    ACCOUNT_NOT_ACTIVATE(30006, "账号未激活"),
    /**
     * 参数有误
     */
    PARAM_ERROR(10001, "参数有误"),
    /**
     * 请求方法不正确
     */
    METHODE_ERROR(10002, "请求方法不正确");

    /**
     * code状态码
     */
    private final Integer code;
    /**
     * 相应信息摘要
     */
    private final String message;
}

4. 添加登录过滤器

package top.mengshuo.core.filter;

import com.auth0.jwt.interfaces.Claim;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import lombok.AllArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import top.mengshuo.core.entity.AuthenticationEntity;
import top.mengshuo.core.utils.JwtTokenUtil;

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

/**
 * 自定义认证过滤器
 *
 * @author mengshuo
 * @since 2021-10-21
 */
@AllArgsConstructor
public class AuthorizedFilter extends OncePerRequestFilter {
    private static final String TOKEN_HANDLER_NAME = "Authentication";
    private static final String TOKEN_PREFIX = "token:";

    private final ObjectMapper objectMapper;

    /**
     * 认证过滤器
     *
     * @param request     请求
     * @param response    响应
     * @param filterChain 过滤器链
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader(TOKEN_HANDLER_NAME);
        // 判断token是否为空 并且以指定的前缀开头
        if (!ObjectUtils.isEmpty(token) && token.startsWith(TOKEN_PREFIX)) {
            // 去除前缀并解析token
            Map<String, Claim> claimMap = JwtTokenUtil.decodeAccessToken(token.replace(TOKEN_PREFIX, ""));
            // json目标类型 AuthenticationEntity
            // 是权限信息类型为了反序列化使用
            // 如果安全等级要求较高 请不要在token中存放敏感信息
            CollectionType type = this.objectMapper.getTypeFactory().constructCollectionType(List.class, AuthenticationEntity.class);
            // 判断token信息是否为空
            if (!ObjectUtils.isEmpty(claimMap)) {
                // 设置认证信息
                SecurityContextHolder.getContext().setAuthentication(
                        new UsernamePasswordAuthenticationToken(
                                claimMap.get(JwtTokenUtil.USER_NAME).asString(),
                                null,
                                this.objectMapper.readValue(claimMap.get(JwtTokenUtil.POWERS).asString(), type)
                        )
                );
            }
        }
        // 继续执行
        filterChain.doFilter(request, response);

    }
}

- AuthenticationEntity 实体 `如果安全等级要求较高jwt中不应放置敏感信息`
package top.mengshuo.core.entity;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;

/**
 * 自定义实现 {@link org.springframework.security.core.GrantedAuthority} 默认实现类中没有setter方法无法反序列化
 * 为了权限信息可以正常反序列化
 *
 * @author mengshuo
 * @since 2021-04-14
 */
@NoArgsConstructor
@AllArgsConstructor
public final class AuthenticationEntity implements GrantedAuthority {

    private static final long serialVersionUID = -4556311025364519896L;
    private String authority;


    @Override
    public String getAuthority() {
        return this.authority;
    }

    public void setAuthority(String authority) {
        this.authority = authority;
    }
}

5. Security核心配置

> 这里对拦截以及方形规则配置进行了抽离                                                                                                                                                                                      `@EnableGlobalMethodSecurity(prePostEnabled = true)` 启用基于方法的权限注解
package top.mengshuo.core.config.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import top.mengshuo.core.entity.SystemProperty;
import top.mengshuo.core.exception.SecurityNotLoginExceptionHandler;
import top.mengshuo.core.filter.AuthorizedFilter;

import javax.annotation.Resource;

/**
 * SpringSecurity核心配置
 *
 * @author mengshuo
 * @since 2021-10-21
 */
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {


    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private SystemProperty systemProperty;

    /**
     * 配置拦截和放行规则
     *
     * @param http http
     * @throws Exception exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /*
            1. 关闭csrf
            2. 关闭表单登录
            3. 配置放行规则
            4. 配置拦截规则
            5. 配置session创建策略为不创建
            6. 配置自定义过滤器 在 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter 执行之前
         */
        http.csrf().disable()
                .formLogin().disable()
                .authorizeRequests().antMatchers(this.systemProperty.getSecurityIgnoreUrls().toArray(new String[]{})).permitAll()
                .and().authorizeRequests().antMatchers(this.systemProperty.getSecurityInterceptUrls().toArray(new String[]{})).authenticated()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//                .and().exceptionHandling().authenticationEntryPoint(new SecurityNotLoginExceptionHandler())
                .and().addFilterBefore(new AuthorizedFilter(this.objectMapper), UsernamePasswordAuthenticationFilter.class)
        ;

    }

    /**
     * 配置密码加解密策略
     *
     * @return 加解密器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置认证管理器
     *
     * @return the {@link AuthenticationManager} to use
     * @throws Exception exception
     */
    @Bean(BeanIds.AUTHENTICATION_MANAGER)
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
}

- 系统配置信息
package top.mengshuo.core.entity;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author mengshuo
 * @since 2021-10-21
 */
@ConfigurationProperties(prefix = "system")
@Component
@Data
public class SystemProperty {
    /**
     * 无需鉴权接口 公共api
     */
    private List<String> securityIgnoreUrls;
    /**
     * 需要鉴权接口
     */
    private List<String> securityInterceptUrls;
}

6. 自定义认证

> 继承`UserDetailsService`类实现`UserDetailsService`方法                                                                                                                                                          由 `authenticationManager.authenticate`进行认证 认证管理器会调用 `loadUserByUsername` 方法加载正确的用户信息 最后对传入的信息和加载出的信息进行对比 如果不一致则会抛出异常
package top.mengshuo.token.service.impl;

import com.auth0.jwt.interfaces.Claim;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
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;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import top.mengshuo.core.entity.Tokens;
import top.mengshuo.core.enums.HttpResultCode;
import top.mengshuo.core.exception.SystemException;
import top.mengshuo.core.utils.JwtTokenUtil;
import top.mengshuo.token.entity.SysUser;
import top.mengshuo.token.service.AuthService;
import top.mengshuo.token.service.SysUserService;
import top.mengshuo.token.vo.AuthVo;

import javax.annotation.Resource;
import java.util.Map;

/**
 * @author mengshuo
 * @since 2021-10-21
 */
@Service
public class AuthServiceImpl implements AuthService, UserDetailsService {
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private SysUserService service;

    @Resource
    private PasswordEncoder encoder;

    /**
     * 自定义认证
     *
     * @param authVo 认证信息
     * @return res
     */
    @Override
    public Tokens auth(AuthVo authVo) {
        // 手动提交认证 认证失败后会交给异常处理器
        Authentication authenticate = this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authVo.getUsername(), authVo.getPassword()));

        // 创建token 并返回
        return JwtTokenUtil.generatorDoubleToken(new SysUser().setUsername(authVo.getUsername()), authenticate.getAuthorities());
    }

    /**
     * 刷新token
     *
     * @param tokens token
     * @return res
     */
    @Override
    public Tokens refresh(Tokens tokens) {
        Map<String, Claim> claimMap =
                JwtTokenUtil.decodeRefreshToken(tokens.getRefreshToken());
        if (ObjectUtils.isEmpty(claimMap)) {
            throw new SystemException(HttpResultCode.PARAM_ERROR.getCode(), "无效的 refreshToken");
        }
        // 从数据库查询用户信息、权限信息 这里没有查询权限信息 直接写固定了
        SysUser sysUser = this.service.lambdaQuery().eq(SysUser::getUsername, claimMap.get(JwtTokenUtil.USER_NAME).asString()).one();
        // 创建新token 并返回
        return JwtTokenUtil.generatorDoubleToken(new SysUser().setUsername(sysUser.getUsername()), AuthorityUtils.createAuthorityList("sys:test:select"));
    }


    /**
     * 根据用户名加载用户信息 最后交由认证管理器去认证
     *
     * @param username 用户名
     * @return 用户信息
     * @throws UsernameNotFoundException 异常
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库查询用户信息、权限信息 这里没有查询权限信息 直接写固定了
        SysUser sysUser = this.service.lambdaQuery().eq(SysUser::getUsername, username).one();
        // 访问数据库获取信息
        return new User(sysUser.getUsername(), sysUser.getPassword(), AuthorityUtils.createAuthorityList("sys:test:select"));
    }


}

- **`AuthService接口`** 
package top.mengshuo.token.service;

import top.mengshuo.core.entity.Tokens;
import top.mengshuo.token.vo.AuthVo;

/**
 * 认证相关
 *
 * @author mengshuo
 * @since 2021-10-21
 */
public interface AuthService {

    /**
     * 自定义认证
     *
     * @param authVo 认证信息
     * @return res
     */
    Tokens auth(AuthVo authVo);

    /**
     * 刷新token
     *
     * @param tokens token
     * @return res
     */
    Tokens refresh(Tokens tokens);
}

7. 配置拦截规则

server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/double_token?serverTimezone=GMT%2B8&characterEncoding=utf8
    username: root
    password: root
  # 关闭资源映射 不会再映射404页面等静态资源
  web:
    resources:
      add-mappings: false
  # 404等错误交由全局异常处理
  mvc:
    throw-exception-if-no-handler-found: true
system:
  # 拦截api
  security-intercept-urls:
    - /v1/**
  # 放行api
  security-ignore-urls:
    - /v1/auth/**

三、测试&验证

1. 添加测试接口等文件

> mapper记得开启@MapperScan 或者使用@Mapper注解
package top.mengshuo.token.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import top.mengshuo.core.entity.HttpResult;
import top.mengshuo.core.entity.Tokens;
import top.mengshuo.token.service.AuthService;
import top.mengshuo.token.vo.AuthVo;

import javax.annotation.Resource;

/**
 * @author mengshuo
 * @since 2021-10-21
 */
@RequestMapping("/v1/auth")
@RestController
public class AuthController {


    @Resource
    private AuthService service;

    @PostMapping("/login")
    public HttpResult<?> auth(@RequestBody AuthVo authVo) {
        return HttpResult.success("认证成功", this.service.auth(authVo));
    }

    @PostMapping("/refresh")
    public HttpResult<?> refresh(@RequestBody Tokens tokens) {
        return HttpResult.success("刷新成功", this.service.refresh(tokens));
    }

    @PreAuthorize("hasAuthority('sys:test:select')")
    @PostMapping("/select")
    public HttpResult<?> select() {
        return HttpResult.success("sys:test:select");
    }

    @PreAuthorize("hasAuthority('sys:test:add')")
    @PostMapping("/add")
    public HttpResult<?> add() {
        return HttpResult.success("sys:test:add");
    }
}

package top.mengshuo.token.service;

import com.baomidou.mybatisplus.extension.service.IService;
import top.mengshuo.token.entity.SysUser;

/**
 * @author mengshuo
 * @since 2021-10-21
 */
public interface SysUserService extends IService<SysUser> {
}

package top.mengshuo.token.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import top.mengshuo.token.entity.SysUser;
import top.mengshuo.token.mapper.SysUserMapper;
import top.mengshuo.token.service.SysUserService;

/**
 * @author mengshuo
 * @since 2021-10-21
 */
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper,SysUser> implements SysUserService {
}

package top.mengshuo.token.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import top.mengshuo.token.entity.SysUser;

/**
 * @author mengshuo
 * @since 2021-10-21
 */
public interface SysUserMapper extends BaseMapper<SysUser> {
}


2. 涉及的代码Demo: coding

1
博主关闭了所有页面的评论