持续创作,加快生长!这是我参与「日新方案 10 月更文挑战」的第31天,点击检查活动详情
认证授权中心
添加依靠
<!-- spring web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring data redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- spring cloud security -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<!-- spring cloud oauth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<!-- spring cloud 依靠 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
application.yml装备
server:
port: 8888 # 端口
spring:
application:
name: oauth2-server # 运用名
# 数据库
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/demo?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false
# Redis
redis:
port: 6379
host: 127.0.0.1
timeout: 3000
database: 1
password:
# Oauth2
client:
oauth2:
client-id: appId # 客户端标识 ID
secret: 123456 # 客户端安全码
# 授权类型
grant_types:
- password
- refresh_token
# token 有用时间,单位秒
token-validity-time: 2592000
refresh-token-validity-time: 2592000
# 客户端拜访规模
scopes:
- api
- all
# Mybatis
mybatis:
configuration:
map-underscore-to-camel-case: true # 开启驼峰映射
Security装备
装备运用Redis存储Token信息
装备暗码的加密、解密、校验逻辑
初始化认证办理目标
装备恳求拜访的放行和认证规矩
import cn.hutool.crypto.digest.DigestUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
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.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import javax.annotation.Resource;
/**
* Security装备
*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* 注入Redis衔接工厂
*/
@Resource
private RedisConnectionFactory redisConnectionFactory;
/**
* 初始化RedisTokenStore,用于将token存储至Redis
*
* @return
*/
@Bean
public RedisTokenStore redisTokenStore() {
RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
// 设置key的层级前缀
redisTokenStore.setPrefix("TOKEN:");
return redisTokenStore;
}
/**
* 初始化暗码编码器,指定编码与校验规矩,用MD5加密暗码
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
// Security官方推荐的BCryptPasswordEncoder加密与校验类
// 密钥的迭代次数(默许为10)
//return new BCryptPasswordEncoder(10);
return new PasswordEncoder() {
/**
* 加密
* @param rawPassword 原始暗码
* @return
*/
@Override
public String encode(CharSequence rawPassword) {
return DigestUtil.md5Hex(rawPassword.toString());
}
/**
* 校验暗码
* @param rawPassword 原始暗码
* @param encodedPassword 加密暗码
* @return
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return DigestUtil.md5Hex(rawPassword.toString()).equals(encodedPassword);
}
};
}
/**
* 初始化认证办理目标
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 放行和认证规矩
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用csrf
http.csrf().disable()
.authorizeRequests()
// 放行的恳求
.antMatchers("/oauth/**", "/actuator/**").permitAll()
.and()
.authorizeRequests()
// 其他恳求必须认证才干拜访
.anyRequest().authenticated();
}
}
登录认证装备
创立UserService类完成UserDetailsService类重写loadUserByUsername办法,该办法首要完成登录、认证校验逻辑,这里简略模仿。
public interface UserMapper {
/**
* 根据用户名 or 手机号 or 邮箱查询用户信息
* @param account
* @return
*/
@Select("select id, username, phone, email, password, roles from user where " +
"(username = #{account} or phone = #{account} or email = #{account})")
Diners selectByAccountInfo(@Param("account") String account);
}
@Service
public class UserService implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StrUtil.hasBlank(username)) {
throw new RuntimeException("用户名不可为空");
}
User user= userMapper.selectByAccountInfo(username);
if (user == null) {
throw new UsernameNotFoundException("用户名或暗码过错,请从头输入");
}
return new User(username, user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(diners.getRoles()));
}
}
Oauth2参数装备类
读取application.yaml文件中的Oauth2装备信息,并封装到ClientOAuth2DataConfiguration类
package com.example.demo.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 客户端装备类
*/
@Component
@ConfigurationProperties(prefix = "client.oauth2")
@Data
public class ClientOAuth2DataConfiguration {
/**
* 客户端标识ID
*/
private String clientId;
/**
* 客户端安全码
*/
private String secret;
/**
* 授权类型
*/
private String[] grantTypes;
/**
* token有用期
*/
private int tokenValidityTime;
/**
* refresh-token有用期
*/
private int refreshTokenValidityTime;
/**
* 客户端拜访规模
*/
private String[] scopes;
}
授权服务装备
package com.example.demo.config;
import com.example.demo.service.UserService;
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.store.redis.RedisTokenStore;
import javax.annotation.Resource;
/**
* 授权服务装备
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
/**
* RedisTokenSore
*/
@Resource
private RedisTokenStore redisTokenStore;
/**
* 认证办理目标
*/
@Resource
private AuthenticationManager authenticationManager;
/**
* 暗码编码器
*/
@Resource
private PasswordEncoder passwordEncoder;
/**
* 客户端装备类
*/
@Resource
private ClientOAuth2DataConfiguration clientOAuth2DataConfiguration;
/**
* 登录校验
*/
@Resource
private UserService userService;
/**
* 装备令牌端点安全束缚
*
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 答应拜访token的公钥,默许/oauth/token_key是受维护的
security.tokenKeyAccess("permitAll()")
// 答应检查token的状况,默许/oauth/check_token是受维护的
.checkTokenAccess("permitAll()");
}
/**
* 客户端装备 - 授权模型
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient(clientOAuth2DataConfiguration.getClientId()) // 客户端标识 ID
.secret(passwordEncoder.encode(clientOAuth2DataConfiguration.getSecret())) // 客户端安全码
.authorizedGrantTypes(clientOAuth2DataConfiguration.getGrantTypes()) // 授权类型
.accessTokenValiditySeconds(clientOAuth2DataConfiguration.getTokenValidityTime()) // token 有用期
.refreshTokenValiditySeconds(clientOAuth2DataConfiguration.getRefreshTokenValidityTime()) // 改写 token 的有用期
.scopes(clientOAuth2DataConfiguration.getScopes()); // 客户端拜访规模
}
/**
* 装备授权以及令牌的拜访端点和令牌服务
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 认证器
endpoints.authenticationManager(authenticationManager)
// 具体登录的办法
.userDetailsService(userService)
// token 存储的方法:Redis
.tokenStore(redisTokenStore);
}
}
履行测验
恳求 localhost:8888/oauth/token
参数设置 履行恳求 检查Redis
增强令牌
增强令牌就是丰厚、自定义令牌包含的信息,这部分信息是客户端能直接看到的
重构端点
重构/oauth/token
端点
/**
* Oauth2控制器
*/
@RestController
@RequestMapping("oauth")
public class OAuthController {
@Resource
private TokenEndpoint tokenEndpoint;
@Resource
private HttpServletRequest request;
/**
* 自定义Token回来目标
*
* @param principal
* @param parameters
* @return
* @throws HttpRequestMethodNotSupportedException
*/
@PostMapping("token")
public HashMap<String, Object> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
OAuth2AccessToken auth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) auth2AccessToken;
Map<String, Object> data = new LinkedHashMap(token.getAdditionalInformation());
data.put("accessToken", token.getValue());
data.put("expireIn", token.getExpiresIn());
data.put("scopes", token.getScope());
if (token.getRefreshToken() != null) {
data.put("refreshToken", token.getRefreshToken().getValue());
}
data.put("path", request.getServletPath());
return BaseUtil.back(1, data);
}
}
履行测验
重构令牌
创立SignInIdentity登录认证目标类完成UserDetails
package com.example.demo.model;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 登录认证目标
*/
@Getter
@Setter
public class SignInIdentity implements UserDetails {
/**
* 主键
*/
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 昵称
*/
private String nickname;
/**
* 暗码
*/
private String password;
/**
* 手机号
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 头像
*/
private String avatarUrl;
/**
* 人物
*/
private String roles;
/**
* 是否有用 0=无效 1=有用
*/
private int isValid;
/**
* 人物集合, 不能为空
*/
private List<GrantedAuthority> authorities;
/**
* 获取人物信息
*
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (StrUtil.isNotBlank(this.roles)) {
String[] strings = this.roles.split(",");
// 获取数据库中的人物信息
this.authorities = Stream.of(strings).map(role -> {
return new SimpleGrantedAuthority(role);
}).collect(Collectors.toList());
} else {
// 如果人物为空则设置为ROLE_USER
this.authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
}
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.isValid != 0;
}
}
修正登录认证
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StrUtil.hasBlank(username)) {
throw new RuntimeException("用户名不可为空");
}
Diners diners = dinersMapper.selectByAccountInfo(username);
if (diners == null) {
throw new UsernameNotFoundException("用户名或暗码过错,请从头输入");
}
// 初始化登录认证目标
SignInIdentity signInIdentity = new SignInIdentity();
// 拷贝属性
BeanUtils.copyProperties(diners, signInIdentity);
return signInIdentity;
// return new User(username, diners.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(diners.getRoles()));
}
令牌增强
/**
* 装备授权以及令牌的拜访端点和令牌服务
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 认证器
endpoints.authenticationManager(authenticationManager)
// 具体登录的办法
.userDetailsService(userService)
// token 存储的方法:Redis
.tokenStore(redisTokenStore)
// 令牌增强目标,增强回来的结果
.tokenEnhancer((accessToken, authentication) -> {
// 获取登录用户的信息,然后设置
SignInIdentity signInIdentity = (SignInIdentity) authentication.getPrincipal();
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("nickname", signInIdentity.getNickname());
map.put("avatarUrl", signInIdentity.getAvatarUrl());
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
token.setAdditionalInformation(map);
return token;
});
}
履行测验
恳求 localhost:8888/oauth/token
资源服务中心
登录成功,得到token,经过token获取资源
认证反常装备
创立MyAuthenticationEntryPoint类,处理认证失利出现反常时的处理逻辑。
package com.example.demo.config;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.example.demo.utils.BaseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
/**
* 认证失利处理
*/
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Resource
private ObjectMapper objectMapper;
/**
* 认证失利处理逻辑
*
* @param request
* @param response
* @param authException
* @throws IOException
*/
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
// 回来 JSON
response.setContentType("application/json;charset=utf-8");
// 状况码 401
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// 写出
PrintWriter out = response.getWriter();
String errorMessage = authException.getMessage();
if (StrUtil.isBlank(errorMessage)) {
errorMessage = "登录失效!";
}
HashMap<String, Object> result = BaseUtil.back(0, errorMessage, errorMessage);
// ResultInfo result = ResultInfoUtil.buildError(ApiConstant.ERROR_CODE, errorMessage, request.getRequestURI());
out.write(objectMapper.writeValueAsString(result));
out.flush();
out.close();
}
}
创立资源服务
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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 javax.annotation.Resource;
/**
* 资源服务
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Resource
private MyAuthenticationEntryPoint authenticationEntryPoint;
/**
* 装备放行的资源
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//一切恳求必须认证经过
http.authorizeRequests()
//其他地址需要认证授权;
.anyRequest()
.authenticated()
.and()
//下边的途径放行
.requestMatchers()
.antMatchers("/user/**");
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.authenticationEntryPoint(authenticationEntryPoint);
}
}
提供资源
package com.example.demo.controller;
import com.example.demo.model.SignInIdentity;
import com.example.demo.utils.BaseUtil;
import io.micrometer.core.instrument.util.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
/**
* 用户中心
*/
@RestController
public class UserController {
@Resource
private RedisTokenStore redisTokenStore;
/**
* 获取登录用户的信息
*
* @param authentication
* @return
*/
@GetMapping("user/getLoginUser")
public HashMap<String, Object> getCurrentUser(Authentication authentication) {
SignInIdentity signInIdentity = (SignInIdentity) authentication.getPrincipal();
HashMap<String, Object> map = new HashMap<>();
map.put("username", signInIdentity.getUsername());
map.put("phone", signInIdentity.getPhone());
map.put("email", signInIdentity.getEmail());
return BaseUtil.back(1, "获取资源成功", map);
}
/**
* 安全退出
*
* @param access_token
* @param authorization
* @return
*/
@GetMapping("user/logout")
public HashMap<String, Object> logout(String access_token, String authorization) {
// 判别access_token是否为空,为空将authorization赋值给access_token
if (StringUtils.isBlank(access_token)) {
access_token = authorization;
}
// 判别authorization是否为空
if (StringUtils.isBlank(access_token)) {
return BaseUtil.back(1, "退出成功");
}
// 判别bearer token是否为空
if (access_token.toLowerCase().contains("bearer ".toLowerCase())) {
access_token = access_token.toLowerCase().replace("bearer ", "");
}
// 铲除redis token信息
OAuth2AccessToken oAuth2AccessToken = redisTokenStore.readAccessToken(access_token);
if (oAuth2AccessToken != null) {
redisTokenStore.removeAccessToken(oAuth2AccessToken);
OAuth2RefreshToken refreshToken = oAuth2AccessToken.getRefreshToken();
redisTokenStore.removeRefreshToken(refreshToken);
}
return BaseUtil.back(1, "退出成功");
}
}
履行测验
恳求localhost:8888/oauth/token
获取token
{
"code": 1,
"message": "Successful.",
"data": {
"nickname": "test",
"avatarUrl": "/test",
"accessToken": "2cf71a49-1f62-4e93-b27c-7cb0b4419ab4",
"expireIn": 2588653,
"scopes": [
"api"
],
"refreshToken": "154aefe0-a0fa-43d4-91fb-c70b1e2998e4",
"path": "/oauth/token"
}
}
运用token获取服务资源,有两种方法:
方法一:
恳求localhost:8888/user/getLoginUser?access_token=2cf71a49-1f62-4e93-b27c-7cb0b4419ab4
获取资源
{
"msg": "获取资源成功",
"code": 1,
"data": {
"phone": "13666666666",
"email": null,
"username": "test"
}
}
方法二:
恳求localhost:8888/user/getLoginUser
,运用Bearer auth认证
获取资源
{
"msg": "获取资源成功",
"code": 1,
"data": {
"phone": "13666666666",
"email": null,
"username": "test"
}
}
校验token
恳求 localhost:8888/oauth/check_token?token=2cf71a49-1f62-4e93-b27c-7cb0b4419ab4
校验token成功时:
{
"avatarUrl": "/test",
"user_name": "test",
"scope": [
"api"
],
"nickname": "test",
"active": true,
"exp": 1629734432,
"authorities": [
"ROLE_USER"
],
"client_id": "appId"
}
校验token失利时:
{
"error": "invalid_token",
"error_description": "Token was not recognised"
}
安全退出
1.恳求localhost:8888/user/logout?access_token=2cf71a49-1f62-4e93-b27c-7cb0b4419ab4
2.恳求localhost:8888/user/logout
,运用Bearer auth认证
{
"msg": "退出成功",
"code": 1,
"data": null
}
退出后再次恳求资源
{
"msg": "Invalid access token: 2cf71a49-1f62-4e93-b27c-7cb0b4419ab4",
"code": 0,
"data": "Invalid access token: 2cf71a49-1f62-4e93-b27c-7cb0b4419ab4"
}
网关校验
添加依靠
<!-- spring cloud gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
装备application.yml
server:
port: 9999
spring:
application:
name: gateway-server
cloud:
gateway:
discovery:
locator:
enabled: true # 开启装备注册中心进行路由功用
lower-case-service-id: true # 将服务称号转小写
routes:
- id: oauth2-server
uri: lb://oauth2-server
predicates:
- Path=/auth/**
filters:
- StripPrefix=1
# 自定义参数
secure:
ignore:
urls: # 装备白名单途径
- /actuator/**
- /auth/oauth/**
- /user/getLoginUser
- /user/logout
# 装备 Eureka Server 注册中心
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://localhost:8080/eureka/
途径白名单装备类
/**
* 网关白名单装备
*/
@Data
@Component
@ConfigurationProperties(prefix = "secure.ignore")
public class IgnoreUrlsConfig {
private List<String> urls;
}
网关过滤器
package com.example.demo.gateway.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.example.demo.utils.BaseUtil;
import com.example.demo.config.IgnoreUrlsConfig;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
/**
* 网关大局过滤器
*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Resource
private IgnoreUrlsConfig ignoreUrlsConfig;
@Resource
private RestTemplate restTemplate;
/**
* 身份校验处理
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 判别当前的恳求是否在白名单中
AntPathMatcher pathMatcher = new AntPathMatcher();
boolean flag = false;
String path = exchange.getRequest().getURI().getPath();
for (String url : ignoreUrlsConfig.getUrls()) {
if (pathMatcher.match(url, path)) {
flag = true;
break;
}
}
// 白名单放行
if (flag) {
return chain.filter(exchange);
}
// 获取 access_token
String access_token = exchange.getRequest().getQueryParams().getFirst("access_token");
// 判别access_token是否为空
if (StringUtils.isBlank(access_token)) {
return this.writeError(exchange, "请登录");
}
// 校验token是否有用
String checkTokenUrl = "http://oauth2-server/oauth/check_token?token=".concat(access_token);
try {
// 发送远程恳求,验证 token
ResponseEntity<String> entity = restTemplate.getForEntity(checkTokenUrl, String.class);
// token无效业务逻辑处理
if (entity.getStatusCode() != HttpStatus.OK) {
return this.writeError(exchange, "恳求失利");
}
if (StringUtils.isBlank(entity.getBody())) {
return this.writeError(exchange, "获取token失利");
}
} catch (Exception e) {
return this.writeError(exchange, "token校验失利");
}
// 放行
return chain.filter(exchange);
}
/**
* 网关过滤器的排序,数字越小优先级越高
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
@Resource
private ObjectMapper objectMapper;
public Mono<Void> writeError(ServerWebExchange exchange, String error) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
HashMap<String, Object> back = BaseUtil.back(0, error);
String resultInfoJson;
DataBuffer buffer = null;
try {
resultInfoJson = objectMapper.writeValueAsString(back);
buffer = response.bufferFactory().wrap(resultInfoJson.getBytes(StandardCharsets.UTF_8));
} catch (JsonProcessingException ex) {
ex.printStackTrace();
}
return response.writeWith(Mono.just(buffer));
}
}
履行测验
恳求localhost:9999/auth/oauth/token
获取token
{
"code": 1,
"message": "Successful.",
"data": {
"nickname": "test",
"avatarUrl": "/test",
"accessToken": "7a3d7102-39eb-4d02-be4c-9a705c9db616",
"expireIn": 2591999,
"scopes": [
"api"
],
"refreshToken": "6c974314-a13c-473a-8cdc-fb8373b3cce5",
"path": "/oauth/token"
}
}
恳求localhost:9999/auth/user/getLoginUser?access_token=7a3d7102-39eb-4d02-be4c-9a705c9db616
获取服务资源
{
"msg": "获取资源成功",
"code": 1,
"data": {
"phone": "13666666666",
"email": null,
"username": "test"
}
}
恳求localhost:9999/auth/user/logout?access_token=7a3d7102-39eb-4d02-be4c-9a705c9db616
安全退出
{
"msg": "退出成功",
"code": 1,
"data": null
}