介绍之类的不在多说了直奔主题
一、添加依赖
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> {
}