参考链接:
Oauth2.0简单解释
Oauth2.0四种方式
什么是JWT
JWT无状态登录
Spring security 系列15篇
Spring boot security 学习
Spring Security Oauth2 permitAll()还校验token
源码分析
UsernamePasswordAuthenticationFilter
前言
网上oauth2相关的demo讲的很笼统,几乎都是内存配置的方式简单演示了一下。
这段时间踩了很多坑,因此整理出了这篇文章
本文解决了如下问题:
- 前后端分离方式,进行oauth2配置,登录成功失败等全部处理,全部返回json
- 使用oauth的数据库方式配置
- 去除框架中的权限判断默认前缀 ROLE_
- 跨域问题,以及需要放行option请求
- jwt方式存储token,登出后token仍旧有效,采用登录成功时将token保存在redis中,登出或修改密码等操作删除redis中的token。并添加jwt过滤器,每次访问接口对token进行校验
- 资源服务器调用check_token接口,服务器直接500了,其实只是授权服务器发现token无效返回了400
未解决的问题:
1. 使用nginx做了负载均衡,搭建了两台资源服务器和两台认证服务器A B,登录资源服务器登录时调用了认证服务器A,获取授权码时(/oauth/authorize)调用了认证服务器B,返回401,尚不知道如何解决?
背景
客户使用多个系统,每个系统都有自己的账号密码,想通过我们的门户系统进行登录授权(其他系统登需要登录时,跳转门户进行登录授权)
服务器
前端服务器
资源服务器(子系统)
认证服务器
认证服务器和资源服务器分为两个项目(模块)
security oauth2框架的交互手段
前端与资源服务器交互(是否登录过):通过session交互
前端与资源服务器交互(每次都会调用认证服务器的/check_token):通过token(header中的Authorization属性)交互
认证流程

流程demo
- 前端服务器访问后端(资源服务器) 活动报名接口(/active)
- 资源服务器会调用认证服务器的/check_token接口,进行校验header中的token校验
- 校验不通过,返回401,前端跳转到登录页面。进行登录
- 登录成功,前端调用 http://127.0.0.1:8080/oauth/authorize?response_type=code&client_id=website&redirect_uri=http://192.168.10.182:8008/web-portal/su/token&scope=all 进行授权(自动授权)
- 认证成功,认证服务器回调: http://127.0.0.1:8008/web-portal/su/token?code=授权码
- 前端拿到返回的token信息,放到header中,与子系统进行交互
依赖问题
最初使用spring boot oauth2依赖有问题(据说不再维护)
因此采用spring-cloud-starter-oauth2的依赖
文章只提供oauth2 jwt相关的依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
数据库配置
表含义这里不多做介绍了,都是security oauth2框架使用数据库模式需要的表,认证配置的表是:oauth_client_details ,需要在该表中配置客户端id,回调URL等信息
CREATE TABLE oauth_access_token (
token_id VARCHAR(256) NULL DEFAULT NULL,
token TEXT NULL DEFAULT NULL,
authentication_id VARCHAR(128) NOT NULL PRIMARY KEY,
user_name VARCHAR(256) NULL DEFAULT NULL,
client_id VARCHAR(256) NULL DEFAULT NULL,
authentication text NULL DEFAULT NULL,
refresh_token VARCHAR(256) NULL DEFAULT NULL);
ALTER TABLE public.oauth_access_token OWNER to dna_portal;
CREATE TABLE oauth_approvals (
userId VARCHAR(256) NULL DEFAULT NULL,
clientId VARCHAR(256) NULL DEFAULT NULL,
scope VARCHAR(256) NULL DEFAULT NULL,
status VARCHAR(10) NULL DEFAULT NULL,
expiresAt time NULL DEFAULT NULL,
lastModifiedAt time NULL DEFAULT NULL);
ALTER TABLE public.oauth_approvals OWNER to dna_portal;
CREATE TABLE oauth_client_details (
client_id VARCHAR(128) NOT NULL PRIMARY KEY,
resource_ids VARCHAR(256) NULL DEFAULT NULL,
client_secret VARCHAR(256) NULL DEFAULT NULL,
scope VARCHAR(256) NULL DEFAULT NULL,
authorized_grant_types VARCHAR(256) NULL DEFAULT NULL,
web_server_redirect_uri VARCHAR(256) NULL DEFAULT NULL,
authorities VARCHAR(256) NULL DEFAULT NULL,
access_token_validity INT8 NULL DEFAULT NULL,
refresh_token_validity INT8 NULL DEFAULT NULL,
additional_information VARCHAR(4096) NULL DEFAULT NULL,
autoapprove VARCHAR(256) NULL DEFAULT NULL);
ALTER TABLE public.oauth_client_details OWNER to dna_portal;
insert into oauth_client_details (client_id,resource_ids,client_secret,scope,authorized_grant_types,
web_server_redirect_uri,authorities,access_token_validity,refresh_token_validity,additional_information,autoapprove) values
('website', 'website', '$2a$10$.ebjcgCVOHuEscJ6xLyQcu21nW93XuHZ2qk2TRbTofDLVhPY0C5S2', 'all', 'authorization_code,refresh_token',
'http://127.0.0.1:8080/web-portal/su/token', 'admin,ROLE_admin',36000, null, null, true);
CREATE TABLE oauth_client_token (
token_id VARCHAR(256) NULL DEFAULT NULL,
token text NULL DEFAULT NULL,
authentication_id VARCHAR(128) NOT NULL PRIMARY KEY,
user_name VARCHAR(256) NULL DEFAULT NULL,
client_id VARCHAR(256) NULL DEFAULT NULL);
ALTER TABLE public.oauth_client_token OWNER to dna_portal;
CREATE TABLE oauth_code (
code VARCHAR(256) NULL DEFAULT NULL,
authentication text NULL DEFAULT NULL);
ALTER TABLE public.oauth_code OWNER to dna_portal;
CREATE TABLE oauth_refresh_token (
token_id VARCHAR(256) NULL DEFAULT NULL,
token text NULL DEFAULT NULL,
authentication text NULL DEFAULT NULL);
ALTER TABLE public.oauth_refresh_token OWNER to dna_portal;
资源服务器配置
跨域配置
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
/**
* 跨域访问配置
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}
资源服务器配置
# 本应用占用端口
server:
port: 8008
servlet:
context-path: /web-portal
session:
cookie:
name: OAUTH2-PORTAL-SESSIONID
oauth2:
server:
# oauth_client_details配置的client_id
clientId: website
# oauth_client_details配置的client_secret(明文)
clientSecret: 2020
# oauth2-server服务的地址
tokenAddr: http://127.0.0.1:8080/oauth2-server/oauth/token
checkTokenAddr: http://127.0.0.1:8080/oauth2-server/oauth/check_token
# jwt密钥
jwtSecret: drinkless-jwt-key
import com.drinkless.portal.filter.JWTAuthenticationFilter;
import com.drinkless.portal.handlder.ApiAccessDeniedHandler;
import com.drinkless.portal.handlder.AuthExceptionEntryPoint;
import com.drinkless.portal.handlder.CustomerResponseErrorHandler;
import com.drinkless.portal.handlder.LoginExpireHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.web.client.RestTemplate;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Value("${oauth2.server.checkTokenAddr}")
private String checkTokenAddr;
@Value("${oauth2.server.clientId}")
private String clientId;
@Value("${oauth2.server.clientSecret}")
private String clientSecret;
@Value("${oauth2.server.jwtSecret}")
private String jwtSecret;
//放行接口 swagger,系统连接性检查 和一些不需要登录就可访问的接口
public static String[] passUrl = {"/su/token/**",
"/region/getTree",
"/app/status",
"/sd/getDictList/**",
"/version",
"/article/getList",
"/article/find/**",
"/advertisement/list",
"/category/getCategoryList",
"/category/find/**",
"/advertisement/list",
"/_health",
"/v2/api-docs", "/swagger-resources/configuration/ui","/swagger-resources", "/swagger-resources/configuration/security",
"/swagger-ui.html","/css/**", "/js/**","/images/**", "/webjars/**", "**/favicon.ico"};
@Bean
@LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder builder){
RestTemplate restTemplate = builder.build();
/*为RestTemplate配置异常处理器0*/
restTemplate.setErrorHandler(new CustomerResponseErrorHandler());
return restTemplate;
}
//不使用权限校验的ROLE_前缀 (http.servletApi().rolePrefix("");该方式无效果,使用@Bean方式)
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
}
@Autowired
private RestTemplate restTemplate;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(clientId);
resources.tokenStore(new JwtTokenStore(accessTokenConverter())).stateless(true);
/* 配置令牌验证 */
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
remoteTokenServices.setRestTemplate(restTemplate);
remoteTokenServices.setCheckTokenEndpointUrl(checkTokenAddr);
remoteTokenServices.setClientId(clientId);
remoteTokenServices.setClientSecret(clientSecret);
resources.tokenServices(remoteTokenServices).stateless(true);
//check_token异常类
resources.authenticationEntryPoint(new AuthExceptionEntryPoint());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(jwtSecret);
return converter;
}
/* 配置资源拦截规则 */
@Override
public void configure(HttpSecurity http) throws Exception {
//关闭csrf
http.cors().and().csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
//放行接口配置
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll() // 对option不校验
.antMatchers(passUrl).permitAll()
.anyRequest().authenticated();
//jwt校验
http.addFilterBefore(new JWTAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class);
//登录超时未登录处理
http.exceptionHandling().authenticationEntryPoint(new LoginExpireHandler());
//权限不足处理器 配合注解使用@Secured("admin")
http.exceptionHandling().accessDeniedHandler(new ApiAccessDeniedHandler());
}
}
JWT过滤器
package com.drinkless.portal.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.drinkless.portal.common.constants.RedisConstant;
import com.drinkless.portal.common.entity.vo.SysUserVo;
import com.drinkless.portal.common.utils.ResultUtil;
import com.drinkless.portal.common.utils.SysUserUtil;
import com.drinkless.portal.config.ResourceServerConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
/**
* jwt过滤器 配合redis校验
*/
@Slf4j
public class JWTAuthenticationFilter implements Filter {
private final ObjectMapper mapper = new ObjectMapper();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("【JWT过滤器】start");
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//如果是放行接口,构造出无token的request后直接放行(这样做的话不会调用/oauth/check_token接口进行校验)
String uri = request.getRequestURI();
log.info("uri_{}:{}, request.getContentType(): {}", request.getMethod(), uri, request.getContentType());
if (!isNeedFilter(uri, ResourceServerConfig.passUrl)) {
request = new HttpServletRequestWrapper(request) {
private Set<String> headerNameSet;
@Override
public Enumeration<String> getHeaderNames() {
if (headerNameSet == null) {
// first time this method is called, cache the wrapped request's header names:
headerNameSet = new HashSet<>();
Enumeration<String> wrappedHeaderNames = super.getHeaderNames();
while (wrappedHeaderNames.hasMoreElements()) {
String headerName = wrappedHeaderNames.nextElement();
if (!"Authorization".equalsIgnoreCase(headerName)) {
headerNameSet.add(headerName);
}
}
}
return Collections.enumeration(headerNameSet);
}
@Override
public Enumeration<String> getHeaders(String name) {
if ("Authorization".equalsIgnoreCase(name)) {
return Collections.<String>emptyEnumeration();
}
return super.getHeaders(name);
}
@Override
public String getHeader(String name) {
if ("Authorization".equalsIgnoreCase(name)) {
return null;
}
return super.getHeader(name);
}
};
filterChain.doFilter(request, response);
} else {
//非放行的接口
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
//是否过期
boolean expireFlag = false;
try {
if (StringUtils.isNotBlank(header)) {
//token串
String token = header.substring(header.lastIndexOf("bearer") + 8);
// 获取ValueOperations bean
ServletContext context = request.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
ValueOperations valueOperations = (ValueOperations) ctx.getBean("valueOperations");
SysUserUtil sysUserUtil = (SysUserUtil) ctx.getBean("sysUserUtil");
SysUserVo user = sysUserUtil.getSysUserVoByToken(token);
String redisToken = (String) valueOperations.get(RedisConstant.REDIS_LOGIN + user.getUsername());
// token失效 或 token不正确
if (StringUtils.isBlank(redisToken) || !token.equals(redisToken)) {
expireFlag = true;
}
} else {
// 没有token
expireFlag = true;
}
} catch (Exception e) {
expireFlag = true;
log.error("【jwtToken】校验出错,异常信息:{}", e);
}
if (expireFlag) {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write(this.mapper.writeValueAsString(ResultUtil.errorResult(
HttpStatus.UNAUTHORIZED.value(), "token失效")));
} else {
filterChain.doFilter(request, response);
}
}
}
/**
* @param uri
* @Description: 是否需要过滤
*/
public boolean isNeedFilter(String uri, String[] includeUrls) {
boolean isNeedFilter = true;
for (String includeUrl : includeUrls) {
includeUrl = includeUrl.replace("/**","");
if (includeUrl.equals(uri) || uri.contains(includeUrl)) {
isNeedFilter = false;
break;
}
}
return isNeedFilter;
}
}
权限不足处理器
package com.drinkless.portal.handlder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.drinkless.portal.common.enums.HttpStatusEnum;
import com.drinkless.portal.common.result.Result;
import com.drinkless.portal.common.utils.ResultUtil;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 403 权限不足处理器
*/
public class ApiAccessDeniedHandler implements AccessDeniedHandler {
public final ObjectMapper MAPPER = new ObjectMapper();
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setStatus(HttpStatusEnum.FORBIDDEN.getCode());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
Result result = ResultUtil.errorResult(HttpStatusEnum.FORBIDDEN.getCode(),
"不允许访问");
PrintWriter out = response.getWriter();
out.write(MAPPER.writeValueAsString(result));
out.flush();
out.close();
}
}
check_token异常类
package com.drinkless.portal.handlder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.drinkless.portal.common.enums.HttpStatusEnum;
import com.drinkless.portal.common.result.Result;
import com.drinkless.portal.common.utils.ResultUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
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;
/**
* check_token异常类
*/
@Component
public class AuthExceptionEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws ServletException {
Map<String, Object> map = new HashMap<String, Object>();
Throwable cause = authException.getCause();
response.setStatus(HttpStatusEnum.UNAUTHORIZED.getCode());
response.setHeader("Content-Type", "application/json;charset=UTF-8");
try {
if (cause instanceof InvalidTokenException) {
Result result = ResultUtil.errorResult(HttpStatusEnum.UNAUTHORIZED.getCode(),
"token失效");
response.getWriter().write(new ObjectMapper().writeValueAsString(result));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
restTemplate异常处理
package com.drinkless.portal.handlder;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.ResponseErrorHandler;
import java.io.IOException;
/**
* restTemplate调用check_toke接口,token错误或失效,回返回400,抛出异常
* 我们不想要抛异常,想让我们自定义的AuthExceptionEntryPoint去处理
* 所以重写了hasError方法
*/
public class CustomerResponseErrorHandler implements ResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
// 这里返回false
return false;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
}
}
登录超时 未登录处理器
package com.drinkless.portal.handlder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.drinkless.portal.common.enums.HttpStatusEnum;
import com.drinkless.portal.common.result.Result;
import com.drinkless.portal.common.utils.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description: 登录超时 未登录处理器
* @Date: 2020/12/09 13:41
*/
@Slf4j
public class LoginExpireHandler implements AuthenticationEntryPoint {
public final ObjectMapper MAPPER = new ObjectMapper();
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setStatus(HttpStatusEnum.UNAUTHORIZED.getCode());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
Result result = ResultUtil.errorResult(HttpStatusEnum.UNAUTHORIZED.getCode(),
"登录过期或未登录");
response.getWriter().write(MAPPER.writeValueAsString(result));
}
}
controller
package com.drinkless.portal.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.drinkless.portal.common.constants.RedisConstant;
import com.drinkless.portal.common.entity.dto.TokenDTO;
import com.drinkless.portal.common.entity.dto.UpdatePasswordDTO;
import com.drinkless.portal.common.entity.vo.SysUserVo;
import com.drinkless.portal.common.enums.ExceptionEnum;
import com.drinkless.portal.common.enums.ResultEnum;
import com.drinkless.portal.common.result.Result;
import com.drinkless.portal.common.service.SysUserService;
import com.drinkless.portal.common.utils.ResultUtil;
import com.drinkless.portal.common.utils.SysUserUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.*;
import org.springframework.security.access.annotation.Secured;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.validation.Valid;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
* @Description: 用户接口
* @Date: 2019/11/27 14:44
*/
@Slf4j
@RestController
@RequestMapping("su")
@Api(description = "系统用户相关接口")
public class SysUserController {
@Autowired
private ValueOperations valueOperations;
@Autowired
private RestTemplate restTemplate;
@Autowired
private SysUserService sysUserService;
@Autowired
private SysUserUtil sysUserUtil;
@Value("${oauth2.server.tokenAddr}")
private String tokenAddr;
@Value("${oauth2.server.clientId}")
private String clientId;
@Value("${oauth2.server.clientSecret}")
private String clientSecret;
// mapper 声明
private final ObjectMapper mapper = new ObjectMapper();
@GetMapping("token")
@ApiOperation("获取token")
public Result submitLogin(String code) throws Exception {
RequestEntity httpEntity = new RequestEntity<>(getHttpBody(code), getHttpHeaders(),
HttpMethod.POST, URI.create(tokenAddr));
ResponseEntity<TokenDTO> exchange = restTemplate.exchange(httpEntity, TokenDTO.class);
if (exchange.getStatusCode().is2xxSuccessful()) {
// redis管理jwtToken失效
TokenDTO tokenDTO = exchange.getBody();
String accessToken = tokenDTO.getAccessToken();
SysUserVo user = sysUserUtil.getSysUserVoByToken(accessToken);
String expiresIn = tokenDTO.getExpiresIn();
valueOperations.set(RedisConstant.REDIS_LOGIN + user.getUsername(), accessToken, Long.parseLong(expiresIn), TimeUnit.SECONDS);
return ResultUtil.successResult(ResultEnum.SUCCESS_STATUS, exchange.getBody());
}
throw new RuntimeException("请求令牌失败!");
}
private MultiValueMap<String, String> getHttpBody(String code) throws UnsupportedEncodingException {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("code", code);
params.add("grant_type", "authorization_code");
String redirectUri = sysUserService.selectOauthRedirectUri(clientId);
params.add("redirect_uri", redirectUri);
params.add("scope", "all");
return params;
}
private HttpHeaders getHttpHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setBasicAuth(clientId, clientSecret);
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
return httpHeaders;
}
//密码重置功能只允许管理员调用
//sprngboot启动类还需要添加以下配置,才可生效
//@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@GetMapping("password/reset/{id}")
@ApiOperation("密码重置")
@Secured("admin")
public Result resetPassword(@PathVariable("id") Long id) {
try {
sysUserService.resetPassword(id);
return ResultUtil.successResult(ResultEnum.SUCCESS_STATUS);
} catch (Exception e) {
log.error("【用户管理】密码重置异常, 异常信息:{}", e);
return ResultUtil.errorResult(ExceptionEnum.UNKNOWN_EXCEPTION);
}
}
@GetMapping("userInfo")
@ApiOperation("获取用户信息接口")
public Result<SysUserVo> queryUserInfo() {
try {
SysUserVo vo = null;
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (StringUtils.isNotBlank(header)) {
//token串
String token = header.substring(header.lastIndexOf("bearer") + 8);
String tokenBody = JwtUtils.testJwt(token);
//token串转对象
JSONObject user = JSON.parseObject(tokenBody).getJSONObject("user");
vo = JSON.toJavaObject(user, SysUserVo.class);
}
return ResultUtil.successResult(ResultEnum.SUCCESS_STATUS, vo);
} catch (Exception e) {
log.error("【用户管理】查询用户信息异常, 异常信息:{}", e);
return ResultUtil.errorResult(ExceptionEnum.UNKNOWN_EXCEPTION);
}
}
}
token对象
/**
* 授权成功token返回体
*/
@Data
public class TokenDTO {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("refresh_token")
private String refreshToken;
@JsonProperty("token_type")
private String tokenType;
@JsonProperty("expires_in")
private String expiresIn;
@JsonProperty("scope")
private String scope;
}
jwt工具类
package com.drinkless.oauth2.server.utils;
import org.springframework.stereotype.Component;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
@Component
public class JwtUtils {
/* 默认head */
public static final String DEFAULT_HEADER = "{\"alg\": \"HS256\",\"typ\": \"JWT\"}";
/* token有效时间 1天 */
public static final long EXPIRE_TIME = 1000*60*60*24;
/* token在header中的名字 */
public static final String HEADER_TOKEN_NAME = "Authorization";
/** Base64URL 编码 */
public static String encode(String input) {
return Base64.getUrlEncoder().encodeToString(input.getBytes());
}
/** Base64URL 解码 */
public static String decode(String input) {
return new String(Base64.getUrlDecoder().decode(input));
}
/**
* HmacSHA256 加密算法
* @param data 要加密的数据
* @param secret 秘钥
*/
public static String HMACSHA256(String data, String secret) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
}
return sb.toString().toUpperCase();
}
/** 获取签名 */
public static String getSignature(String payload, String secret) throws Exception {
return HMACSHA256(encode(DEFAULT_HEADER)+"."+encode(payload),secret);
}
/**
* 验证jwt,正确返回载体数据,错误返回null
* @param jwt
*/
public static String testJwt(String jwt) throws Exception {
String[] jwts = jwt.split("\\.");
return decode(jwts[1]);
}
}
资源服务器的配置到这里结束
认证服务器配置
server:
port: 8068
servlet:
context-path: /oauth2-server
oauth2:
server:
# jwt密钥
jwtSecret: drinkless-jwt-key
授权服务配置
package com.drinkless.oauth2.server.config;
import com.drinkless.oauth2.server.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
/**
* 授权服务配置
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;
@Autowired
private DataSource dataSource;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 配置一个客户端
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource).passwordEncoder(passwordEncoder);
}
/** 配置token管理 */
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain chain = new TokenEnhancerChain();
List<TokenEnhancer> list = new ArrayList<>();
list.add(jwtTokenEnhancer);
list.add(jwtAccessTokenConverter);
chain.setTokenEnhancers(list);
//通过注入密码授权被打开AuthenticationManager
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore)
//刷新令牌授权将包含对用户详细信息的检查,以确保该帐户仍然活动,因此需要配置userDetailsService
.userDetailsService(userDetailsService)
//配置令牌生成
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(chain);
// //该字段设置设置refresh token是否重复使用,true:reuse;false:no reuse.
// .reuseRefreshTokens(true)
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()") //允许所有人请求令牌
.checkTokenAccess("isAuthenticated()") //已验证的客户端才能请求check_token端点
.allowFormAuthenticationForClients();
// 单点登录配置
// security.tokenKeyAccess("isAuthenticated()");
}
}
安全框架配置类
package com.drinkless.oauth2.server.config;
import com.drinkless.oauth2.server.handlder.LoginExpireHandler;
import com.drinkless.oauth2.server.handlder.LoginFailureHandler;
import com.drinkless.oauth2.server.handlder.LoginSuccessHandler;
import com.drinkless.oauth2.server.handlder.LogoutSuccessHandler;
import com.drinkless.oauth2.server.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
/**
* @Description: 安全框架配置类
* @Date: 2020/11/30 21:03
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭csrf
http.cors().and().csrf().disable();
// 授权配置
/* 开启授权认证 */
http.authorizeRequests()
.antMatchers("/oauth/**").permitAll() //允许访问授权接口
.anyRequest().authenticated();
//登录页面
http.formLogin().loginProcessingUrl("/login");
// 登录成功或失败处理
http.formLogin().successHandler(new LoginSuccessHandler())
.failureHandler(new LoginFailureHandler());
// 登出授权及处理
http.logout().logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler(tokenStore)).permitAll();
//登录超时 未登录
http.exceptionHandling().authenticationEntryPoint(new LoginExpireHandler());
}
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
// 自动建表
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
/** 授权服务配置需要用到这个bean */
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/** 加密算法 */
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
jwt token配置
package com.drinkless.oauth2.server.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.io.IOException;
/**
* jwt token配置
*/
@Configuration
public class JwtTokenStoreConfig {
@Value("${oauth2.server.jwtSecret}")
public String jwtSecret;
@Bean
public TokenStore jwtTokenStore()throws IOException {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() throws IOException {
JwtAccessTokenConverter accessTokenConverter = new
JwtAccessTokenConverter();
//配置JWT使用的秘钥
accessTokenConverter.setSigningKey(jwtSecret);
return accessTokenConverter;
}
@Bean
public JwtTokenEnhancer jwtTokenEnhancer() {
return new JwtTokenEnhancer();
}
}
jwt token 定制
package com.drinkless.oauth2.server.config;
import com.drinkless.oauth2.server.entity.SysUser;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import java.util.HashMap;
import java.util.Map;
/**
* jwt token 定制
*/
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
SysUser sysUser = (SysUser) oAuth2Authentication.getPrincipal();
Map<String, Object> map = new HashMap<>();
sysUser.setPassword(null);
map.put("user", sysUser);
((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(map);
return oAuth2AccessToken;
}
}
userDetailsService实现类(框架会根据返回用户信息中的密码校验和前端(request中的password)传的是否一致)
package com.drinkless.oauth2.server.service;
import com.drinkless.oauth2.server.entity.SysUser;
import com.drinkless.oauth2.server.exception.MyUsernameNotFoundException;
import com.drinkless.oauth2.server.mapper.SysUserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.bcrypt.BCrypt;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
/**
* @Description: userDetailsService实现类
* @Date: 2020/11/30 17:12
*/
@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private HttpServletRequest request;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = sysUserMapper.selectByUsername(username);
if (sysUser == null) {
throw new MyUsernameNotFoundException(username + " is not exists!");
}
if (!BCrypt.checkpw(request.getParameter("password"), sysUser.getPassword())) {
throw new MyUsernameNotFoundException(username + " password error!");
}
return sysUser;
}
}
@Data
public class SysUser implements UserDetails {
/* 用户id */
private Long id;
/* 用户名 */
private String username;
/* 密码 */
private String password;
private String nickname;
private String province;
private String serverNos;
private String idCardNo;
private Date birthDate;
private String gender;
private String phone;
private Boolean status;
private Boolean deleteFlag;
private Date createDatetime;
private Date updateDatetime;
/* 角色列表 */
private List<SysRole> authorities = new ArrayList<>();
/* 指示是否未过期的用户的凭据(密码),过期的凭据防止认证 默认true 默认表示未过期 */
private boolean credentialsNonExpired = true;
//账户是否未过期,过期无法验证 默认true表示未过期
private boolean accountNonExpired = true;
//用户是未被锁定,锁定的用户无法进行身份验证 默认true表示未锁定
private boolean accountNonLocked = true;
//是否可用 ,禁用的用户不能身份验证 默认true表示可用
private boolean enabled = true;
}
登录成功处理器
package com.drinkless.oauth2.server.handlder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.drinkless.oauth2.server.entity.SysRole;
import com.drinkless.oauth2.server.entity.SysUser;
import com.drinkless.oauth2.server.enums.ResultEnum;
import com.drinkless.oauth2.server.utils.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* @Description: 登录成功处理
* @Date: 2020/11/30 21:48
*/
@Slf4j
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
public final ObjectMapper MAPPER = new ObjectMapper();
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
SysUser user = (SysUser) authentication.getPrincipal();
log.info("【登录】用户名:{}登录成功", user.getUsername());
log.info("【登录】权限:{}", user.getAuthorities());
// 返回权限
HashMap<Object, Object> map = new HashMap<>();
List<SysRole> authorities = user.getAuthorities();
List<String> roles = new ArrayList<>();
for (SysRole role : authorities) {
roles.add(role.getAuthority());
}
map.put("roles", roles);
// 返回给前端
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().write(MAPPER.writeValueAsString(ResultUtil.successResult(ResultEnum.SUCCESS_STATUS, map)));
}
}
登录失败处理器
package com.drinkless.oauth2.server.handlder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.drinkless.oauth2.server.entity.Result;
import com.drinkless.oauth2.server.enums.ExceptionEnum;
import com.drinkless.oauth2.server.utils.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录失败处理器
*/
@Slf4j
public class LoginFailureHandler implements AuthenticationFailureHandler {
public final ObjectMapper MAPPER = new ObjectMapper();
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
Result result = ResultUtil.errorResult(ExceptionEnum.UNKNOWN_EXCEPTION.getCode(),
"登录失败");
String username = request.getParameter("username");
if (e.getMessage().contains("is not exists") || e.getMessage().contains("password error")) {
result = ResultUtil.errorResult(ExceptionEnum.UNKNOWN_EXCEPTION.getCode(),
"用户或密码错误");
}
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().write(MAPPER.writeValueAsString(result));
log.error("【登录失败】用户名:{}", username);
}
}
登录超时 未登录处理器
package com.drinkless.oauth2.server.handlder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.drinkless.oauth2.server.entity.Result;
import com.drinkless.oauth2.server.enums.HttpStatusEnum;
import com.drinkless.oauth2.server.utils.ResultUtil;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description: 登录超时 未登录处理器
* @Date: 2020/12/09 13:41
*/
public class LoginExpireHandler implements AuthenticationEntryPoint {
public final ObjectMapper MAPPER = new ObjectMapper();
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setStatus(HttpStatusEnum.UNAUTHORIZED.getCode());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
Result result = ResultUtil.errorResult(HttpStatusEnum.UNAUTHORIZED.getCode(),
"登录过期或未登录");
response.getWriter().write(MAPPER.writeValueAsString(result));
}
}
登出成功处理
package com.drinkless.oauth2.server.handlder;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.drinkless.oauth2.server.constants.RedisConstant;
import com.drinkless.oauth2.server.entity.Result;
import com.drinkless.oauth2.server.enums.ResultEnum;
import com.drinkless.oauth2.server.utils.JwtUtils;
import com.drinkless.oauth2.server.utils.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
/**
* @Description: 登出成功处理
* @Date: 2020/12/14 13:31
*/
@Slf4j
public class LogoutSuccessHandler implements org.springframework.security.web.authentication.logout.LogoutSuccessHandler {
public final ObjectMapper MAPPER = new ObjectMapper();
private TokenStore tokenStore;
public LogoutSuccessHandler(TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
try {
// 获取用户信息
String header = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);
String token = header.substring(header.lastIndexOf("bearer") + 8);
String tokenBody = JwtUtils.testJwt(token);
JSONObject user = JSON.parseObject(tokenBody).getJSONObject("user");
// 清除redis中的token
ServletContext context = httpServletRequest.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
ValueOperations valueOperations = (ValueOperations) ctx.getBean("valueOperations");
valueOperations.set(RedisConstant.REDIS_LOGIN
+ user.get("username"), "", 1, TimeUnit.MICROSECONDS);
//返回退出成功信息
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
Result result = ResultUtil.successResult(ResultEnum.SUCCESS_STATUS);
PrintWriter out = httpServletResponse.getWriter();
out.write(MAPPER.writeValueAsString(result));
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
找不到用户异常类
package com.drinkless.oauth2.server.exception;
import org.springframework.security.core.AuthenticationException;
/**
* @Description: 找不到用户异常
* @Date: 2020/12/10 14:48
*/
public class MyUsernameNotFoundException extends AuthenticationException {
public MyUsernameNotFoundException(String msg) {
super(msg);
}
public MyUsernameNotFoundException(String msg, Throwable t) {
super(msg, t);
}
}
跨域
@Configuration
public class CorsConfig implements WebMvcConfigurer {
/**
* 跨域访问配置
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}