This commit is contained in:
parent
c3c02a9473
commit
e3aa1b0fd0
|
@ -53,6 +53,21 @@
|
|||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.37.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<!-- 配置冲突,需要排除 -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
|
||||
</dependency>
|
||||
|
||||
<!-- JWT库 -->
|
||||
<dependency>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package com.recovery.admin.boot.config;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.same.SaSameUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Sa-Token 权限认证 配置类
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
// 注册 Sa-Token 全局过滤器
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
log.info("校验是否是网关转发请求:====================");
|
||||
return new SaServletFilter()
|
||||
.addInclude("/**")
|
||||
.addExclude("/favicon.ico")
|
||||
.setAuth(obj -> {
|
||||
// 校验 Same-Token 身份凭证 —— 以下两句代码可简化为:SaSameUtil.checkCurrentRequestToken();
|
||||
String token = SaHolder.getRequest().getHeader(SaSameUtil.SAME_TOKEN);
|
||||
SaSameUtil.checkToken(token);
|
||||
})
|
||||
.setError(e -> {
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
;
|
||||
}
|
||||
}
|
|
@ -35,6 +35,15 @@
|
|||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.37.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
|
@ -46,25 +55,13 @@
|
|||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-jose</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.recovery</groupId>
|
||||
<artifactId>common-web</artifactId>
|
||||
<version>${hoe-version}</version>
|
||||
</dependency>
|
||||
<!-- OAuth2 认证服务器-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth.boot</groupId>
|
||||
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-jose</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
package com.recovery.auth.comm.exception;
|
||||
|
||||
|
||||
import com.recovery.common.base.result.ApiResult;
|
||||
import com.recovery.common.base.result.ResultCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.InternalAuthenticationServiceException;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
|
||||
import org.springframework.security.oauth2.provider.NoSuchClientException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* @author:
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
@Order(-1)
|
||||
public class AuthExceptionHandler {
|
||||
|
||||
/**
|
||||
* 用户不存在
|
||||
*
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(UsernameNotFoundException.class)
|
||||
public ApiResult handleUsernameNotFoundException(UsernameNotFoundException e) {
|
||||
log.error("错误信息:{}", e.getMessage(),e);
|
||||
return ApiResult.failed(ResultCode.USER_NOT_EXIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户名和密码异常
|
||||
*
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(InvalidGrantException.class)
|
||||
public ApiResult handleInvalidGrantException(InvalidGrantException e) {
|
||||
log.error("错误信息:{}", e.getMessage(),e);
|
||||
return ApiResult.failed(ResultCode.USERNAME_OR_PASSWORD_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户名和密码异常
|
||||
*
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(InvalidClientException.class)
|
||||
public ApiResult handleInvalidGrantException(InvalidClientException e) {
|
||||
log.error("错误信息:{}", e.getMessage(),e);
|
||||
return ApiResult.failed(ResultCode.CLIENT_AUTHENTICATION_FAILED);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 账户异常(禁用、锁定、过期)
|
||||
*
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler({InternalAuthenticationServiceException.class})
|
||||
public ApiResult handleInternalAuthenticationServiceException(InternalAuthenticationServiceException e) {
|
||||
log.error("错误信息:{}", e.getMessage(),e);
|
||||
return ApiResult.failed(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* token 无效或已过期
|
||||
*
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler({InvalidTokenException.class})
|
||||
public ApiResult handleInvalidTokenExceptionException(InvalidTokenException e) {
|
||||
log.error("错误信息:{}", e.getMessage(),e);
|
||||
return ApiResult.failed(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* token 无效或已过期
|
||||
*
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler({NoSuchClientException.class})
|
||||
public ApiResult noSuchClientException(NoSuchClientException e) {
|
||||
log.error("错误信息:{}", e.getMessage(),e);
|
||||
return ApiResult.failed(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package com.recovery.auth.comm.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import com.recovery.common.base.constant.SecurityConstants;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* @author:
|
||||
* @date: 2022/5/23
|
||||
*/
|
||||
public class CommonUtils {
|
||||
public static String getOAuth2ClientId() {
|
||||
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
|
||||
String clientId = request.getParameter(SecurityConstants.CLIENT_ID_KEY);
|
||||
if (StrUtil.isNotBlank(clientId)) {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
String basic = request.getHeader(SecurityConstants.AUTHORIZATION_KEY);
|
||||
if (StrUtil.isNotBlank(basic) && basic.startsWith(SecurityConstants.BASIC_PREFIX)) {
|
||||
basic = basic.replace(SecurityConstants.BASIC_PREFIX, Strings.EMPTY);
|
||||
String basicPlainText = new String(Base64.getDecoder().decode(basic.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
|
||||
clientId = basicPlainText.split(":")[0];
|
||||
}
|
||||
return clientId;
|
||||
}
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
package com.recovery.auth.config;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
|
||||
import com.recovery.auth.security.details.client.ClientDetailsServiceImpl;
|
||||
import com.recovery.auth.security.details.user.SysUserDetails;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
|
||||
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.provider.CompositeTokenGranter;
|
||||
import org.springframework.security.oauth2.provider.TokenGranter;
|
||||
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
|
||||
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
|
||||
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
*
|
||||
* @author
|
||||
* @date: 2021/11/24
|
||||
* @description:
|
||||
* @modifiedBy:
|
||||
* @version: 1.0
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAuthorizationServer
|
||||
@RequiredArgsConstructor
|
||||
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final ClientDetailsServiceImpl clientDetailsService;
|
||||
|
||||
/**
|
||||
* OAuth2客户端
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void configure(ClientDetailsServiceConfigurer clients) {
|
||||
clients.withClientDetails(clientDetailsService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
|
||||
*/
|
||||
@Override
|
||||
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
|
||||
// Token增强
|
||||
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
|
||||
List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
|
||||
tokenEnhancers.add(tokenEnhancer());
|
||||
tokenEnhancers.add(jwtAccessTokenConverter());
|
||||
tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
|
||||
|
||||
// 获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者
|
||||
List<TokenGranter> granterList = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));
|
||||
|
||||
CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);
|
||||
endpoints
|
||||
.authenticationManager(authenticationManager)
|
||||
.accessTokenConverter(jwtAccessTokenConverter())
|
||||
.tokenEnhancer(tokenEnhancerChain)
|
||||
.tokenGranter(compositeTokenGranter)
|
||||
.reuseRefreshTokens(true)
|
||||
.tokenServices(tokenServices(endpoints))
|
||||
;
|
||||
}
|
||||
|
||||
public DefaultTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {
|
||||
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
|
||||
List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
|
||||
tokenEnhancers.add(tokenEnhancer());
|
||||
tokenEnhancers.add(jwtAccessTokenConverter());
|
||||
tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
|
||||
|
||||
DefaultTokenServices tokenServices = new DefaultTokenServices();
|
||||
tokenServices.setTokenStore(endpoints.getTokenStore());
|
||||
tokenServices.setSupportRefreshToken(true);
|
||||
tokenServices.setClientDetailsService(clientDetailsService);
|
||||
tokenServices.setTokenEnhancer(tokenEnhancerChain);
|
||||
return tokenServices;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT内容增强
|
||||
*/
|
||||
@Bean
|
||||
public TokenEnhancer tokenEnhancer() {
|
||||
return (accessToken, authentication) -> {
|
||||
Map<String, Object> additionalInfo = CollectionUtil.newHashMap();
|
||||
Object principal = authentication.getUserAuthentication().getPrincipal();
|
||||
if (principal instanceof SysUserDetails){
|
||||
SysUserDetails sysUserDetails = (SysUserDetails) principal;
|
||||
additionalInfo.put("userId", sysUserDetails.getUserId());
|
||||
additionalInfo.put("username", sysUserDetails.getUsername());
|
||||
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
|
||||
}
|
||||
|
||||
return accessToken;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用非对称加密算法对token签名
|
||||
*/
|
||||
@Bean
|
||||
public JwtAccessTokenConverter jwtAccessTokenConverter() {
|
||||
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
|
||||
converter.setKeyPair(keyPair());
|
||||
return converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 密钥库中获取密钥对(公钥+私钥)
|
||||
*/
|
||||
@Bean
|
||||
public KeyPair keyPair() {
|
||||
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "afd123".toCharArray());
|
||||
KeyPair keyPair = factory.getKeyPair("jwt", "afd123".toCharArray());
|
||||
return keyPair;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.recovery.auth.config;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.same.SaSameUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Sa-Token 权限认证 配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
// 注册 Sa-Token 全局过滤器
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
.addInclude("/**")
|
||||
.addExclude("/favicon.ico")
|
||||
.setAuth(obj -> {
|
||||
// 校验 Same-Token 身份凭证 —— 以下两句代码可简化为:SaSameUtil.checkCurrentRequestToken();
|
||||
String token = SaHolder.getRequest().getHeader(SaSameUtil.SAME_TOKEN);
|
||||
SaSameUtil.checkToken(token);
|
||||
})
|
||||
.setError(e -> {
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
package com.recovery.auth.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
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.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
*
|
||||
* @author:
|
||||
* @date: 2021/11/24
|
||||
* @description:
|
||||
* @modifiedBy:
|
||||
* @version: 1.0
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final UserDetailsService sysUserDetailsService;
|
||||
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests().antMatchers("/api/oauth/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.httpBasic()
|
||||
.and()
|
||||
.csrf().disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证管理对象
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||
return super.authenticationManagerBean();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义认证器
|
||||
*
|
||||
* @param auth
|
||||
*/
|
||||
@Override
|
||||
public void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.authenticationProvider(daoAuthenticationProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认的用户名密码认证授权提供者
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||
provider.setUserDetailsService(sysUserDetailsService);
|
||||
provider.setPasswordEncoder(passwordEncoder());
|
||||
provider.setHideUserNotFoundExceptions(false); // 是否隐藏用户不存在异常,默认:true-隐藏;false-抛出异常;
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||
}
|
||||
}
|
|
@ -1,25 +1,25 @@
|
|||
package com.recovery.auth.controller;
|
||||
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import cn.dev33.satoken.stp.SaTokenInfo;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import com.recovery.auth.exception.BusinessException;
|
||||
import com.recovery.auth.feign.UserFeignClient;
|
||||
import com.recovery.auth.security.details.user.JwtAuthenticationRequest;
|
||||
import com.recovery.auth.service.AuthService;
|
||||
import com.recovery.common.base.dto.UserAuthDTO;
|
||||
import com.recovery.common.base.dto.UserAuthorityDto;
|
||||
import com.recovery.common.base.result.ApiResult;
|
||||
import com.recovery.common.base.result.ResultCode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.security.KeyPair;
|
||||
import java.security.Principal;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author:
|
||||
|
@ -32,6 +32,8 @@ public class AuthController {
|
|||
|
||||
@Resource
|
||||
AuthService authService;
|
||||
@Resource
|
||||
UserFeignClient userFeignClient;
|
||||
|
||||
@PostMapping("/token")
|
||||
public ApiResult postAccessToken(@RequestBody JwtAuthenticationRequest authenticationRequest, HttpServletRequest request){
|
||||
|
@ -46,6 +48,32 @@ public class AuthController {
|
|||
return ApiResult.ok(map);
|
||||
}
|
||||
|
||||
@GetMapping("/doLogin")
|
||||
public SaResult doLogin(@RequestBody JwtAuthenticationRequest authenticationRequest) {
|
||||
if(StringUtils.isEmpty(authenticationRequest.getUsername())){
|
||||
throw new BusinessException("账户不能为空");
|
||||
}
|
||||
if(StringUtils.isEmpty(authenticationRequest.getPassword())){
|
||||
throw new BusinessException("密码不能为空");
|
||||
}
|
||||
|
||||
ApiResult<UserAuthDTO> result = userFeignClient.getUserByUsername(authenticationRequest.getUsername());
|
||||
UserAuthDTO userDetails = new UserAuthDTO();
|
||||
if (ResultCode.SUCCESS.getCode().equals(result.getCode())) {
|
||||
userDetails = result.getData();
|
||||
}
|
||||
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
|
||||
if (userDetails.getUserName().equals(authenticationRequest.getUsername()) && userDetails.getPassword().equals(authenticationRequest.getPassword())) {
|
||||
log.info("密码校验成功!");
|
||||
StpUtil.login(userDetails.getUserName());
|
||||
}else {
|
||||
return SaResult.error("密码错误");
|
||||
}
|
||||
// 第3步,返回给前端
|
||||
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
|
||||
return SaResult.ok("登录成功").setData(tokenInfo);
|
||||
}
|
||||
|
||||
// @GetMapping("/public-key")
|
||||
// public Map<String, Object> getPublicKey() {
|
||||
// //单例模式
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
package com.recovery.auth.security.details.client;
|
||||
|
||||
|
||||
|
||||
import com.recovery.common.base.enums.PasswordEncoderTypeEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.oauth2.provider.ClientDetailsService;
|
||||
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
*
|
||||
* @author: AI码师
|
||||
* @date: 2021/11/24
|
||||
* @description:
|
||||
* @modifiedBy:
|
||||
* @version: 1.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ClientDetailsServiceImpl implements ClientDetailsService {
|
||||
@Override
|
||||
@Cacheable(cacheNames = "auth", key = "'oauth-client:'+#clientId")
|
||||
public ClientDetails loadClientByClientId(String clientId) {
|
||||
// 后面通过feign从管理端获取,目前写死
|
||||
BaseClientDetails clientDetails = new BaseClientDetails(
|
||||
"hoe",
|
||||
"",
|
||||
"all",
|
||||
"password,client_credentials,refresh_token,authorization_code",
|
||||
"",
|
||||
"http://www.baidu.com"
|
||||
|
||||
);
|
||||
clientDetails.setClientSecret(PasswordEncoderTypeEnum.NOOP.getPrefix() + "hoe");
|
||||
clientDetails.setAccessTokenValiditySeconds(3600);
|
||||
clientDetails.setRefreshTokenValiditySeconds(36000000);
|
||||
return clientDetails;
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
package com.recovery.auth.security.details.user;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class JwtAuthenticationRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -8445943548965154778L;
|
||||
|
||||
private String username;
|
||||
private String phone;
|
||||
private String password;
|
||||
private String verifyCode;
|
||||
private String loginMethod;
|
||||
private String visitorsType;
|
||||
|
||||
public JwtAuthenticationRequest(String username,String phone,String password,String verifyCode,String loginMethod,String visitorsType) {
|
||||
this.username = username;
|
||||
this.phone = phone;
|
||||
this.password = password;
|
||||
this.verifyCode = verifyCode;
|
||||
this.loginMethod = loginMethod;
|
||||
this.visitorsType = visitorsType;
|
||||
}
|
||||
|
||||
public JwtAuthenticationRequest() {
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getUserPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setUserPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getVerifyCode() {
|
||||
return verifyCode;
|
||||
}
|
||||
|
||||
public void setVerifyCode(String verifyCode) {
|
||||
this.verifyCode = verifyCode;
|
||||
}
|
||||
|
||||
public String getLoginMethod() {
|
||||
return loginMethod;
|
||||
}
|
||||
|
||||
public void setLoginMethod(String loginMethod) {
|
||||
this.loginMethod = loginMethod;
|
||||
}
|
||||
public String getVisitorsType() {
|
||||
return visitorsType;
|
||||
}
|
||||
|
||||
public void setVisitorsType(String visitorsType) {
|
||||
this.visitorsType = visitorsType;
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package com.recovery.auth.security.details.user;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author:
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SysUserDetails implements UserDetails {
|
||||
|
||||
/**
|
||||
* 扩展字段
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 默认字段
|
||||
*/
|
||||
private String username;
|
||||
private String password;
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* 角色
|
||||
*/
|
||||
private Collection<SimpleGrantedAuthority> authorities;
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return this.authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package com.recovery.auth.security.details.user;
|
||||
|
||||
import com.recovery.auth.feign.UserFeignClient;
|
||||
import com.recovery.common.base.dto.UserAuthDTO;
|
||||
import com.recovery.common.base.enums.PasswordEncoderTypeEnum;
|
||||
import com.recovery.common.base.result.ApiResult;
|
||||
import com.recovery.common.base.result.ResultCode;
|
||||
import com.recovery.common.base.util.RedisUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.AccountExpiredException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.authentication.LockedException;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author:
|
||||
*/
|
||||
@Service("sysUserDetailsService")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class SysUserDetailsServiceImpl implements UserDetailsService {
|
||||
|
||||
|
||||
@Resource
|
||||
private UserFeignClient userFeignClient;
|
||||
@Resource
|
||||
RedisUtil redisUtil;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
// 后面从管理端获取用户信息
|
||||
ApiResult<UserAuthDTO> result = userFeignClient.getUserByUsername(username);
|
||||
SysUserDetails userDetails = null;
|
||||
if (ApiResult.ok().getCode().equals(result.getCode())) {
|
||||
UserAuthDTO user = result.getData();
|
||||
if (null != user) {
|
||||
userDetails = SysUserDetails.builder()
|
||||
.userId(user.getUserId())
|
||||
.username(user.getUserName())
|
||||
//角色
|
||||
// .authorities(handleRoles(user.getRoles()))
|
||||
.enabled(user.getStatus() == 1)
|
||||
.password(PasswordEncoderTypeEnum.BCRYPT.getPrefix() + user.getPassword())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
if (Objects.isNull(userDetails)) {
|
||||
throw new UsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg());
|
||||
} else if (!userDetails.isEnabled()) {
|
||||
throw new DisabledException("该账户已被禁用!");
|
||||
} else if (!userDetails.isAccountNonLocked()) {
|
||||
throw new LockedException("该账号已被锁定!");
|
||||
} else if (!userDetails.isAccountNonExpired()) {
|
||||
throw new AccountExpiredException("该账号已过期!");
|
||||
}
|
||||
return userDetails;
|
||||
}
|
||||
|
||||
private Collection<SimpleGrantedAuthority> handleRoles(List<String> roles) {
|
||||
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
|
||||
for (String role : roles) {
|
||||
authorities.add(new SimpleGrantedAuthority(role));
|
||||
}
|
||||
return authorities;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
package com.recovery.auth.service.impl;
|
||||
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.recovery.auth.exception.BusinessException;
|
||||
import com.recovery.auth.feign.UserFeignClient;
|
||||
import com.recovery.auth.security.details.user.JwtAuthenticationRequest;
|
||||
import com.recovery.auth.security.details.user.SysUserDetails;
|
||||
import com.recovery.auth.service.AuthService;
|
||||
import com.recovery.common.base.dto.UserAuthDTO;
|
||||
import com.recovery.common.base.dto.UserAuthorityDto;
|
||||
|
@ -15,16 +13,11 @@ import com.recovery.common.base.util.EncryptUtil;
|
|||
import com.recovery.common.base.util.RedisUtil;
|
||||
import com.recovery.common.base.utils.JwtUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.AccountExpiredException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.authentication.LockedException;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
|
|
|
@ -48,6 +48,22 @@
|
|||
<artifactId>spring-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- httpClient依赖,缺少此依赖api网关转发请求时可能发生503错误 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
|
||||
<version>1.34.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- MySQL 连接驱动依赖 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package com.recovery.gateway.config;
|
||||
|
||||
|
||||
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure {
|
||||
// 注册 Sa-Token全局过滤器
|
||||
@Bean
|
||||
public SaReactorFilter getSaReactorFilter() {
|
||||
return new SaReactorFilter()
|
||||
// 拦截地址
|
||||
.addInclude("/**") /* 拦截全部path */
|
||||
// 开放地址
|
||||
.addExclude("/favicon.ico")
|
||||
// 鉴权方法:每次访问进入
|
||||
.setAuth(obj -> {
|
||||
// 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
|
||||
SaRouter.match("/**", "/auth/oauth/doLogin", r -> StpUtil.checkLogin());
|
||||
|
||||
// 权限认证 -- 不同模块, 校验不同权限
|
||||
SaRouter.match("/api/test1", r -> StpUtil.checkPermission("api.test1"));
|
||||
SaRouter.match("/api/test2", r -> StpUtil.checkPermission("api.test2"));
|
||||
SaRouter.match("/api/test3", r -> StpUtil.checkRoleOr("admin", "super"));
|
||||
|
||||
// 更多匹配 ... */
|
||||
})
|
||||
// 异常处理方法:每次setAuth函数出现异常时进入
|
||||
.setError(e -> {
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,80 +1,80 @@
|
|||
package com.recovery.gateway.security;
|
||||
|
||||
|
||||
import com.recovery.common.base.constant.SecurityConstants;
|
||||
import com.recovery.common.base.result.ResultCode;
|
||||
import com.recovery.gateway.util.ResponseUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
|
||||
import reactor.core.publisher.Mono;
|
||||
/**
|
||||
* @author:
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class ResourceServerConfig {
|
||||
|
||||
private final ResourceServerManager resourceServerManager;
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
|
||||
http.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint());
|
||||
http.authorizeExchange()
|
||||
.anyExchange().access(resourceServerManager)
|
||||
.and()
|
||||
.exceptionHandling()
|
||||
.accessDeniedHandler(accessDeniedHandler()) // 处理未授权
|
||||
.authenticationEntryPoint(authenticationEntryPoint()) //处理未认证
|
||||
.and().csrf().disable();
|
||||
return http.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义未授权响应
|
||||
*/
|
||||
@Bean
|
||||
ServerAccessDeniedHandler accessDeniedHandler() {
|
||||
return (exchange, denied) -> {
|
||||
Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse()))
|
||||
.flatMap(response -> ResponseUtils.writeErrorInfo(response, ResultCode.ACCESS_UNAUTHORIZED));
|
||||
return mono;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* token无效或者已过期自定义响应
|
||||
*/
|
||||
@Bean
|
||||
ServerAuthenticationEntryPoint authenticationEntryPoint() {
|
||||
return (exchange, e) -> {
|
||||
Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse()))
|
||||
.flatMap(response -> ResponseUtils.writeErrorInfo(response, ResultCode.TOKEN_INVALID_OR_EXPIRED));
|
||||
return mono;
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
|
||||
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
||||
jwtGrantedAuthoritiesConverter.setAuthorityPrefix(SecurityConstants.AUTHORITY_PREFIX);
|
||||
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.JWT_AUTHORITIES_KEY);
|
||||
|
||||
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
||||
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
|
||||
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
|
||||
}
|
||||
}
|
||||
//package com.recovery.gateway.security;
|
||||
//
|
||||
//
|
||||
//import com.recovery.common.base.constant.SecurityConstants;
|
||||
//import com.recovery.common.base.result.ResultCode;
|
||||
//import com.recovery.gateway.util.ResponseUtils;
|
||||
//import lombok.AllArgsConstructor;
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//import org.springframework.core.convert.converter.Converter;
|
||||
//import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
//import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
//import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
//import org.springframework.security.oauth2.jwt.Jwt;
|
||||
//import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
//import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||
//import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
|
||||
//import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
//import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
|
||||
//import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
|
||||
//import reactor.core.publisher.Mono;
|
||||
///**
|
||||
// * @author:
|
||||
// */
|
||||
//@AllArgsConstructor
|
||||
//@Configuration
|
||||
//@EnableWebFluxSecurity
|
||||
//public class ResourceServerConfig {
|
||||
//
|
||||
// private final ResourceServerManager resourceServerManager;
|
||||
//
|
||||
// @Bean
|
||||
// public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
// http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
|
||||
// http.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint());
|
||||
// http.authorizeExchange()
|
||||
// .anyExchange().access(resourceServerManager)
|
||||
// .and()
|
||||
// .exceptionHandling()
|
||||
// .accessDeniedHandler(accessDeniedHandler()) // 处理未授权
|
||||
// .authenticationEntryPoint(authenticationEntryPoint()) //处理未认证
|
||||
// .and().csrf().disable();
|
||||
// return http.build();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 自定义未授权响应
|
||||
// */
|
||||
// @Bean
|
||||
// ServerAccessDeniedHandler accessDeniedHandler() {
|
||||
// return (exchange, denied) -> {
|
||||
// Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse()))
|
||||
// .flatMap(response -> ResponseUtils.writeErrorInfo(response, ResultCode.ACCESS_UNAUTHORIZED));
|
||||
// return mono;
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * token无效或者已过期自定义响应
|
||||
// */
|
||||
// @Bean
|
||||
// ServerAuthenticationEntryPoint authenticationEntryPoint() {
|
||||
// return (exchange, e) -> {
|
||||
// Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse()))
|
||||
// .flatMap(response -> ResponseUtils.writeErrorInfo(response, ResultCode.TOKEN_INVALID_OR_EXPIRED));
|
||||
// return mono;
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
|
||||
// JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
||||
// jwtGrantedAuthoritiesConverter.setAuthorityPrefix(SecurityConstants.AUTHORITY_PREFIX);
|
||||
// jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.JWT_AUTHORITIES_KEY);
|
||||
//
|
||||
// JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
||||
// jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
|
||||
// return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
|
||||
// }
|
||||
//}
|
|
@ -1,122 +1,79 @@
|
|||
package com.recovery.gateway.security;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import com.recovery.common.base.constant.GlobalConstants;
|
||||
import com.recovery.common.base.constant.SecurityConstants;
|
||||
import com.recovery.gateway.util.UrlPatternUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.web.server.authorization.AuthorizationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
*
|
||||
* @author: AI码师 关注公众号"AI码师"获取完整源码
|
||||
* @date: 2021/11/24
|
||||
* @description:
|
||||
* @modifiedBy:
|
||||
* @version: 1.0
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@ConfigurationProperties(prefix = "security")
|
||||
public class ResourceServerManager implements ReactiveAuthorizationManager<AuthorizationContext> {
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
@Setter
|
||||
private List<String> ignoreUrls;
|
||||
|
||||
@Override
|
||||
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
|
||||
ServerHttpRequest request = authorizationContext.getExchange().getRequest();
|
||||
if (request.getMethod() == HttpMethod.OPTIONS) { // 预检请求放行
|
||||
return Mono.just(new AuthorizationDecision(true));
|
||||
}
|
||||
PathMatcher pathMatcher = new AntPathMatcher();
|
||||
String method = request.getMethodValue();
|
||||
String path = request.getURI().getPath();
|
||||
|
||||
// 跳过token校验,放在这里去做是为了能够动态刷新
|
||||
// if (skipValid(path)) {
|
||||
return Mono.just(new AuthorizationDecision(true));
|
||||
// }
|
||||
|
||||
// // 如果token为空 或者token不合法 则进行拦截
|
||||
// String restfulPath = method + ":" + path; // RESTFul接口权限设计 @link https://www.cnblogs.com/haoxianrui/p/14961707.html
|
||||
// String token = request.getHeaders().getFirst(SecurityConstants.AUTHORIZATION_KEY);
|
||||
// if (StrUtil.isBlank(token) || !StrUtil.startWithIgnoreCase(token, SecurityConstants.JWT_PREFIX)) {
|
||||
// return Mono.just(new AuthorizationDecision(false));
|
||||
// }
|
||||
//package com.recovery.gateway.security;
|
||||
//
|
||||
// // 从redis中获取资源权限
|
||||
// Map<String, Object> urlPermRolesRules = redisTemplate.opsForHash().entries(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||
// List<String> authorizedRoles = new ArrayList<>(); // 拥有访问权限的角色
|
||||
// boolean requireCheck = false; // 是否需要鉴权,默认未设置拦截规则不需鉴权
|
||||
//import cn.hutool.core.collection.CollectionUtil;
|
||||
//import cn.hutool.core.convert.Convert;
|
||||
//import cn.hutool.core.util.StrUtil;
|
||||
//
|
||||
// // 获取当前资源 所需要的角色
|
||||
// for (Map.Entry<String, Object> permRoles : urlPermRolesRules.entrySet()) {
|
||||
// String perm = permRoles.getKey();
|
||||
// if (pathMatcher.match(perm, restfulPath)) {
|
||||
// List<String> roles = Convert.toList(String.class, permRoles.getValue());
|
||||
// authorizedRoles.addAll(Convert.toList(String.class, roles));
|
||||
// if (requireCheck == false) {
|
||||
// requireCheck = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//import com.recovery.common.base.constant.GlobalConstants;
|
||||
//import com.recovery.common.base.constant.SecurityConstants;
|
||||
//import com.recovery.gateway.util.UrlPatternUtils;
|
||||
//import lombok.RequiredArgsConstructor;
|
||||
//import lombok.Setter;
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
//import org.springframework.data.redis.core.RedisTemplate;
|
||||
//import org.springframework.http.HttpMethod;
|
||||
//import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
//import org.springframework.security.authorization.AuthorizationDecision;
|
||||
//import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||
//import org.springframework.security.core.Authentication;
|
||||
//import org.springframework.security.core.GrantedAuthority;
|
||||
//import org.springframework.security.web.server.authorization.AuthorizationContext;
|
||||
//import org.springframework.stereotype.Component;
|
||||
//import org.springframework.util.AntPathMatcher;
|
||||
//import org.springframework.util.PathMatcher;
|
||||
//import reactor.core.publisher.Mono;
|
||||
//
|
||||
// // 如果资源不需要权限 则直接返回授权成功
|
||||
// if (!requireCheck) {
|
||||
//import java.util.ArrayList;
|
||||
//import java.util.List;
|
||||
//import java.util.Map;
|
||||
///**
|
||||
// * Created with IntelliJ IDEA.
|
||||
// *
|
||||
// * @author:
|
||||
// * @date: 2021/11/24
|
||||
// * @description:
|
||||
// * @modifiedBy:
|
||||
// * @version: 1.0
|
||||
// */
|
||||
//@Component
|
||||
//@RequiredArgsConstructor
|
||||
//@Slf4j
|
||||
//@ConfigurationProperties(prefix = "security")
|
||||
//public class ResourceServerManager implements ReactiveAuthorizationManager<AuthorizationContext> {
|
||||
// private final RedisTemplate redisTemplate;
|
||||
//
|
||||
// @Setter
|
||||
// private List<String> ignoreUrls;
|
||||
//
|
||||
// @Override
|
||||
// public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
|
||||
// ServerHttpRequest request = authorizationContext.getExchange().getRequest();
|
||||
// if (request.getMethod() == HttpMethod.OPTIONS) { // 预检请求放行
|
||||
// return Mono.just(new AuthorizationDecision(true));
|
||||
// }
|
||||
// PathMatcher pathMatcher = new AntPathMatcher();
|
||||
// String method = request.getMethodValue();
|
||||
// String path = request.getURI().getPath();
|
||||
//
|
||||
// // 判断JWT中携带的用户角色是否有权限访问
|
||||
// Mono<AuthorizationDecision> authorizationDecisionMono = mono
|
||||
// .filter(Authentication::isAuthenticated)
|
||||
// .flatMapIterable(Authentication::getAuthorities)
|
||||
// .map(GrantedAuthority::getAuthority)
|
||||
// .any(authority -> {
|
||||
// String roleCode = authority.substring(SecurityConstants.AUTHORITY_PREFIX.length()); // 用户的角色
|
||||
// boolean hasAuthorized = CollectionUtil.isNotEmpty(authorizedRoles) && authorizedRoles.contains(roleCode);
|
||||
// return hasAuthorized;
|
||||
// })
|
||||
// .map(AuthorizationDecision::new)
|
||||
// .defaultIfEmpty(new AuthorizationDecision(false));
|
||||
// return authorizationDecisionMono;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过校验
|
||||
*
|
||||
* @param path
|
||||
* @return
|
||||
*/
|
||||
private boolean skipValid(String path) {
|
||||
for (String skipPath : ignoreUrls) {
|
||||
if (UrlPatternUtils.match(skipPath, path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// // 跳过token校验,放在这里去做是为了能够动态刷新
|
||||
//// if (skipValid(path)) {
|
||||
// return Mono.just(new AuthorizationDecision(true));
|
||||
//// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 跳过校验
|
||||
// *
|
||||
// * @param path
|
||||
// * @return
|
||||
// */
|
||||
// private boolean skipValid(String path) {
|
||||
// for (String skipPath : ignoreUrls) {
|
||||
// if (UrlPatternUtils.match(skipPath, path)) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//}
|
|
@ -1,62 +1,69 @@
|
|||
package com.recovery.gateway.security;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import com.nimbusds.jose.JWSObject;
|
||||
import com.recovery.common.base.constant.SecurityConstants;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
*
|
||||
* @author: AI码师 关注公众号"AI码师"获取完整源码
|
||||
* @date: 2021/11/24
|
||||
* @description:
|
||||
* @modifiedBy:
|
||||
* @version: 1.0
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityGlobalFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
log.info("接受到请求地址:"+request.getURI());
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
// 不是正确的的JWT不做解析处理
|
||||
String token = request.getHeaders().getFirst(SecurityConstants.AUTHORIZATION_KEY);
|
||||
if (StrUtil.isBlank(token) || !StrUtil.startWithIgnoreCase(token, SecurityConstants.JWT_PREFIX)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
// 解析JWT获取jti,以jti为key判断redis的黑名单列表是否存在,存在则拦截访问
|
||||
token = StrUtil.replaceIgnoreCase(token, SecurityConstants.JWT_PREFIX, Strings.EMPTY);
|
||||
String payload = StrUtil.toString(JWSObject.parse(token).getPayload());
|
||||
request = exchange.getRequest().mutate()
|
||||
.header(SecurityConstants.JWT_PAYLOAD_KEY, URLEncoder.encode(payload, "UTF-8"))
|
||||
.build();
|
||||
exchange = exchange.mutate().request(request).build();
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
//package com.recovery.gateway.security;
|
||||
//
|
||||
//import cn.dev33.satoken.same.SaSameUtil;
|
||||
//import cn.hutool.core.util.StrUtil;
|
||||
//
|
||||
//import com.nimbusds.jose.JWSObject;
|
||||
//import com.recovery.common.base.constant.SecurityConstants;
|
||||
//import lombok.RequiredArgsConstructor;
|
||||
//import lombok.SneakyThrows;
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import org.apache.logging.log4j.util.Strings;
|
||||
//import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
//import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
//import org.springframework.core.Ordered;
|
||||
//import org.springframework.data.redis.core.RedisTemplate;
|
||||
//import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
//import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
//import org.springframework.stereotype.Component;
|
||||
//import org.springframework.web.server.ServerWebExchange;
|
||||
//import reactor.core.publisher.Mono;
|
||||
//
|
||||
//import java.net.URLEncoder;
|
||||
///**
|
||||
// * Created with IntelliJ IDEA.
|
||||
// *
|
||||
// * @author: AI码师 关注公众号"AI码师"获取完整源码
|
||||
// * @date: 2021/11/24
|
||||
// * @description:
|
||||
// * @modifiedBy:
|
||||
// * @version: 1.0
|
||||
// */
|
||||
//@Component
|
||||
//@Slf4j
|
||||
//@RequiredArgsConstructor
|
||||
//public class SecurityGlobalFilter implements GlobalFilter, Ordered {
|
||||
//
|
||||
// @SneakyThrows
|
||||
// @Override
|
||||
// public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
//
|
||||
// ServerHttpRequest request = exchange.getRequest();
|
||||
// log.info("接受到请求地址:"+request.getURI());
|
||||
// ServerHttpResponse response = exchange.getResponse();
|
||||
// // 不是正确的的JWT不做解析处理
|
||||
// String token = request.getHeaders().getFirst(SecurityConstants.AUTHORIZATION_KEY);
|
||||
// if (StrUtil.isBlank(token) || !StrUtil.startWithIgnoreCase(token, SecurityConstants.JWT_PREFIX)) {
|
||||
// return chain.filter(exchange);
|
||||
// }
|
||||
// // 为请求追加 Same-Token 参数
|
||||
// request.mutate()
|
||||
// // 为请求追加 Same-Token 参数
|
||||
// .header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken())
|
||||
// .build();
|
||||
// //结束
|
||||
// // 解析JWT获取jti,以jti为key判断redis的黑名单列表是否存在,存在则拦截访问
|
||||
// token = StrUtil.replaceIgnoreCase(token, SecurityConstants.JWT_PREFIX, Strings.EMPTY);
|
||||
// String payload = StrUtil.toString(JWSObject.parse(token).getPayload());
|
||||
// request = exchange.getRequest().mutate()
|
||||
// .header(SecurityConstants.JWT_PAYLOAD_KEY, URLEncoder.encode(payload, "UTF-8"))
|
||||
// .build();
|
||||
// exchange = exchange.mutate().request(request).build();
|
||||
// return chain.filter(exchange);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public int getOrder() {
|
||||
// return 0;
|
||||
// }
|
||||
//}
|
10
pom.xml
10
pom.xml
|
@ -46,6 +46,16 @@
|
|||
<artifactId>hutool-all</artifactId>
|
||||
<version>${hutool-version}</version>
|
||||
</dependency>
|
||||
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-jackson</artifactId>
|
||||
<version>1.37.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<!-- end -->
|
||||
<dependencyManagement>
|
||||
|
|
Loading…
Reference in New Issue