今天公司要让我搭建一个权限系统 系统分析 要用 springsecurity jwt oauth2 搭建 基于网关gateway 今天这几天的学习 总结了一下吧 希望对以后你们开发有所帮助
校验服务器端
- springsecurity 配置
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 登录失败处理handler,返回一段json
http
.authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.and()
.authorizeRequests().antMatchers("/getPublicKey","/oauth/logout","/oauth/saveUser","/phone/allCode").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable();
}
/**
* 如果不配置SpringBoot会自动配置一个AuthenticationManager,覆盖掉内存中的用户
*/
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
- oauth2 配置 -- 我这是基于短信登录配置的
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.User;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
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 org.springframework.security.web.AuthenticationEntryPoint;
import javax.sql.DataSource;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 授权服务配置
*/
@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private DataSource dataSource;
private AuthenticationManager authenticationManager;
@Autowired
private MyUserDetailServerImpl myUserDetailServer;
/**
* 配置客户端详情(数据库)
*/
@Override
@SneakyThrows
public void configure(ClientDetailsServiceConfigurer clients) {
JdbcClientDetailsServiceImpl jdbcClientDetailsService = new JdbcClientDetailsServiceImpl(dataSource);
jdbcClientDetailsService.setFindClientDetailsSql(AuthConstants.FIND_CLIENT_DETAILS_SQL);
jdbcClientDetailsService.setSelectClientDetailsSql(AuthConstants.SELECT_CLIENT_DETAILS_SQL);
clients.withClientDetails(jdbcClientDetailsService);
}
/**
* 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
tokenEnhancers.add(tokenEnhancer());
tokenEnhancers.add(jwtAccessTokenConverter());
tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(jwtAccessTokenConverter())
.tokenEnhancer(tokenEnhancerChain)
.userDetailsService(myUserDetailServer)
//定义短信登录
.tokenGranter(tokenGranter(endpoints))
// refresh token有两种使用方式:重复使用(true)、非重复使用(false),默认为true
// 1 重复使用:access token过期刷新时, refresh token过期时间未改变,仍以初次生成的时间为准
// 2 非重复使用:access token过期刷新时, refresh token过期时间延续,在refresh token有效期内刷新便永不失效达到无需再次登录的目的
.reuseRefreshTokens(true);
}
/**
* 自定义授权模式
* @param endpoints
* @return
*/
private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
List<TokenGranter> list = new ArrayList<>();
// 这里配置密码模式
if (authenticationManager != null) {
list.add(new ResourceOwnerPasswordTokenGranter(authenticationManager,
endpoints.getTokenServices(),
endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory()));
}
//刷新token模式、
list.add(new RefreshTokenGranter
(endpoints.getTokenServices(),
endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory()));
//自定义手机号验证码模式、
list.add( new ResourceOwnerSmsCodeGranter(
authenticationManager,
endpoints.getTokenServices(),
endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory()));
//授权码模式、
list.add(new AuthorizationCodeTokenGranter(
endpoints.getTokenServices(),
endpoints.getAuthorizationCodeServices(),
endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory()));
//、简化模式
list.add(new ImplicitTokenGranter(
endpoints.getTokenServices(),
endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory()));
//客户端模式
list.add(new ClientCredentialsTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
return new CompositeTokenGranter(list);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
/*security.allowFormAuthenticationForClients();*/
CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security);
endpointFilter.afterPropertiesSet();
endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint());
security.addTokenEndpointAuthenticationFilter(endpointFilter);
security.authenticationEntryPoint(authenticationEntryPoint())
.tokenKeyAccess("isAuthenticated()")
.checkTokenAccess("permitAll()");
}
/**
* 自定义认证异常响应数据
* @return
*/
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, e) -> {
response.setStatus(HttpStatus.HTTP_OK);
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Cache-Control", "no-cache");
ResultVo result = ResultVo.error(403,"认证失败");
response.getWriter().print(JSONUtil.toJsonStr(result));
response.getWriter().flush();
e.printStackTrace();
};
}
/**
* 使用非对称加密算法对token签名
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyPair());
return converter;
}
/**
* 从classpath下的密钥库中获取密钥对(公钥+私钥)
*/
@Bean
public KeyPair keyPair() {
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(
new ClassPathResource("youlai.jks"), "XXXXXX123".toCharArray());
KeyPair keyPair = factory.getKeyPair(
"XXXXX", "XXXXX123456321".toCharArray());
return keyPair;
}
/**
* JWT内容增强
*/
@Bean
public TokenEnhancer tokenEnhancer() {
return (accessToken, authentication) -> {
Map<String, Object> map = new HashMap<>(2);
User user = (User) authentication.getUserAuthentication().getPrincipal();
map.put(AuthConstants.JWT_USER_ID_KEY, user.getUsername());
map.put(AuthConstants.JWT_CLIENT_ID_KEY, user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map);
return accessToken;
};
}
}
- 短信配置
mport java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
/**
* 定义短信登录
*/
public class ResourceOwnerSmsCodeGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "phone";
private final AuthenticationManager authenticationManager;
public ResourceOwnerSmsCodeGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE );
}
protected ResourceOwnerSmsCodeGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticationManager = authenticationManager;
}
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
String username = (String)parameters.get("username");
String password = (String)parameters.get("code");
SmsCheckServiceImpl smsCheckService=new SmsCheckServiceImpl();
//检查短信信息是否正确
smsCheckService.checkCode(username,password);
parameters.remove("code");
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((AbstractAuthenticationToken)userAuth).setDetails(parameters);
try {
userAuth = this.authenticationManager.authenticate(userAuth);
} catch (AccountStatusException var8) {
throw new InvalidGrantException(var8.getMessage());
} catch (BadCredentialsException var9) {
throw new InvalidGrantException(var9.getMessage());
}
if (userAuth != null && userAuth.isAuthenticated()) {
OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
} else {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
}
}
- 异常处理
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter;
import org.springframework.security.web.AuthenticationEntryPoint;
/**
* 重写filter实现客户端自定义异常处理
*/
public class CustomClientCredentialsTokenEndpointFilter extends ClientCredentialsTokenEndpointFilter {
private AuthorizationServerSecurityConfigurer configurer;
private AuthenticationEntryPoint authenticationEntryPoint;
public CustomClientCredentialsTokenEndpointFilter(AuthorizationServerSecurityConfigurer configurer) {
this.configurer = configurer;
}
@Override
public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
super.setAuthenticationEntryPoint(null);
this.authenticationEntryPoint = authenticationEntryPoint;
}
@Override
protected AuthenticationManager getAuthenticationManager() {
return configurer.and().getSharedObject(AuthenticationManager.class);
}
@Override
public void afterPropertiesSet() {
setAuthenticationFailureHandler((request, response, e) -> authenticationEntryPoint.commence(request, response, e));
setAuthenticationSuccessHandler((request, response, authentication) -> {
});
}
}
- 权限校验
/**
* TODO 查询用户的权限信息
* 定义UserDetailsService
*
* @author Administrator
* @date 2020/1/20 14:10
**/
@Service
@AllArgsConstructor
public class MyUserDetailServerImpl implements UserDetailsService {
@Autowired
private SysUserServiceImpl sysUserService;
@Autowired
private PasswordEncoder passwordEncoder;
private HttpServletRequest request;
@Autowired
RedisUtil redisUtil;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (username == null) {
throw new UsernameNotFoundException("用户不存在!!");
}
String clientId = request.getParameter(AuthConstants.JWT_CLIENT_ID_KEY);
String grantType = request.getParameter(AuthConstants.GRANT_TYPE);
return setBaseUserDetail(username, clientId, grantType);
}
public User setBaseUserDetail(String username, String clientId, String grantType) {
SysUser user = new SysUser();
BaseUserDetail userDetails = new BaseUserDetail();
//密码模式
if ("password".equals(grantType) || "refresh_token".equals(grantType)) {
user = sysUserService.getUser(username);
userDetails.setPassword(user.getPassword());
//短信模式
} else if ("phone".equals(grantType)) {
user = sysUserService.getUserByPhone(username);
String data = (String) redisUtil.get(AuthConstants.PHONE_CODE_DETAIL + username);
Gson gson = new Gson();
Map map = gson.fromJson(data, Map.class);
if(map.get("code")==null){
throw new UsernameNotFoundException("验证码不存在!!");
}
String code = String.valueOf(map.get("code"));
code=code.replace(".0","");
user.setPassword(passwordEncoder.encode(code));
userDetails.setPassword(passwordEncoder.encode(code));
username=user.getUserName();
}
GrantedAuthority authority = new RoleDept("username", username);
userDetails.setUsername(username);
userDetails.setId(user.getId());
userDetails.setClientId(clientId);
//获取权限信息
List<String> sysPermission = sysUserService.getSysPermission(username);
//把权限信息放到权限类里
GrantedAuthority interfaces = new RoleDept("interfaces", sysPermission);
return new User(username,userDetails.getPassword(),Arrays.asList(authority, interfaces));
}
}
- 短信校验
@Component
public class SmsCheckServiceImpl {
@Autowired
private SysUserService sysUserService;
public void checkCode(String phone,String code){
if(phone ==null){
throw new UsernameNotFoundException("请填写正确的手机号");
}
if(code ==null){
throw new UsernameNotFoundException("请填验证码");
}
// SysUser userByPhone = sysUserService.getUserByPhone(phone);
// if(userByPhone ==null){
// throw new UsernameNotFoundException("没有发现客户信息");
// }
}
}
- 短信获取验证码
@Service
public class SSMServiceImpl implements SSMService {
@Autowired
RedisUtil redisUtil;
@Value("${sms.accessKeyId}")
String accessKeyId;
@Value("${sms.accessSecret}")
String accessSecret;
@Override
public ResultDataVo getCode(String phone) {
ResultDataVo resultDataVo = new ResultDataVo();
String getDetail = (String) redisUtil.get(AuthConstants.PHONE_CODE_DETAIL + phone);
if(!StringUtil.isNullOrEmpty(getDetail)){
resultDataVo.setData(getDetail);
return resultDataVo;
}else{
Map resultMap=new HashMap<>();
String radom = RandomUtil.getSixBitRandom();
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessSecret);
IAcsClient client = new DefaultAcsClient(profile);
CommonRequest request = new CommonRequest();
request.setSysMethod(MethodType.POST);
request.setSysDomain("dysmsapi.aliyuncs.com");
request.setSysVersion("2017-05-25");
request.setSysAction("SendSms");
request.putQueryParameter("RegionId", "XXXX");
request.putQueryParameter("PhoneNumbers", phone);
request.putQueryParameter("SignName", "XXXX");
request.putQueryParameter("TemplateCode", "XXXX");
request.putQueryParameter("TemplateParam", "{\"code\":\""+radom+"\"}");
try {
CommonResponse response = client.getCommonResponse(request);
Map mapTypes = JSON.parseObject(response.getData());
resultMap.put("phone",phone);
resultMap.put("code",radom);
redisUtil.set(AuthConstants.PHONE_CODE_DETAIL+phone,String.valueOf(resultMap),30000);
String code = (String)mapTypes.get("Code");
if("OK".equals(code)){
resultDataVo.setCode(200);
resultDataVo.setMessage("发送成功");
}
resultDataVo.setData(resultMap);
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
}
return resultDataVo;
}
}
}
- 获取token
mport org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import java.security.Principal;
import java.util.Map;
@Api(tags = "认证中心")
@RestController
@RequestMapping("/oauth")
@AllArgsConstructor
@Slf4j
public class AuthController {
private TokenEndpoint tokenEndpoint;
@ApiOperation("OAuth2认证生成token")
@ApiImplicitParams({
@ApiImplicitParam(name = "grant_type", defaultValue = "password", value = "授权模式", required = true),
@ApiImplicitParam(name = "client_id", defaultValue = "client", value = "Oauth2客户端ID", required = true),
@ApiImplicitParam(name = "client_secret", defaultValue = "123456", value = "Oauth2客户端秘钥", required = true),
@ApiImplicitParam(name = "refresh_token", value = "刷新token"),
@ApiImplicitParam(name = "username", defaultValue = "admin", value = "登录用户名"),
@ApiImplicitParam(name = "password", defaultValue = "123456", value = "登录密码"),
})
@PostMapping("/token")
public ResultDataVo postAccessToken(
@ApiIgnore Principal principal,
@ApiIgnore @RequestParam Map<String, String> parameters
) throws HttpRequestMethodNotSupportedException {
// String clientId = parameters.get(AuthConstants.JWT_CLIENT_ID_KEY);
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
Oauth2Token oauth2Token = Oauth2Token.builder()
.token(oAuth2AccessToken.getValue())
.refreshToken(oAuth2AccessToken.getRefreshToken().getValue())
.expiresIn(oAuth2AccessToken.getExpiresIn())
.build();
return ResultDataVo.ok(oauth2Token);
}
}
- 获取公共key
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.KeyPair;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
@Api(tags = "获取公钥接口")
@RestController
public class PublicKeyController {
@Autowired
private KeyPair keyPair;
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
SysUserService sysUserService;
@GetMapping("/getPublicKey")
public Map<String, Object> loadPublicKey() {
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAKey key = new RSAKey.Builder(publicKey).build();
return new JWKSet(key).toJSONObject();
}
}
配置文件
项目结构
资源服务器
资源服务器和网关放到一起了
- 拦截器
import cn.hutool.core.util.StrUtil;
import com.nimbusds.jose.JWSObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author Administrator
*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private static Logger LOGGER = LoggerFactory.getLogger(AuthGlobalFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (StrUtil.isEmpty(token)) {
return chain.filter(exchange);
}
try {
//从token中解析用户信息并设置到Header中去
String realToken = token.replace("Bearer ", "");
JWSObject jwsObject = JWSObject.parse(realToken);
String userStr = jwsObject.getPayload().toString();
LOGGER.info("AuthGlobalFilter.filter() user:{}",userStr);
ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build();
exchange = exchange.mutate().request(request).build();
} catch (Exception e) {
e.printStackTrace();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
- 资源服务器配置
/**
* 资源服务器配置
*/
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {
@Autowired
private AuthorizationManager authorizationManager;
private WhiteListConfig whiteListConfig;
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http.oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
http.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint());
http.authorizeExchange()
.pathMatchers(ArrayUtil.toArray(whiteListConfig.getUrls(), String.class)).permitAll()
.anyExchange().access(authorizationManager)
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler()) // 处理未授权
.authenticationEntryPoint(authenticationEntryPoint()) //处理未认证
.and().csrf().disable();
return http.build();
}
/**
* 未授权
*
* @return
*/
@Bean
ServerAccessDeniedHandler accessDeniedHandler() {
return (exchange, denied) -> {
Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse()))
.flatMap(response -> {
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getHeaders().set("Access-Control-Allow-Origin", "*");
response.getHeaders().set("Cache-Control", "no-cache");
String body = JSONUtil.toJsonStr(ResultVo.error(403,"访问未授权"));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
return response.writeWith(Mono.just(buffer))
.doOnError(error -> DataBufferUtils.release(buffer));
});
return mono;
};
}
/**
* token无效或者已过期自定义响应
*/
@Bean
ServerAuthenticationEntryPoint authenticationEntryPoint() {
return (exchange, e) -> {
Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse()))
.flatMap(response -> {
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getHeaders().set("Access-Control-Allow-Origin", "*");
response.getHeaders().set("Cache-Control", "no-cache");
String body = JSONUtil.toJsonStr(ResultVo.error(405,"token无效或已过期"));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
return response.writeWith(Mono.just(buffer))
.doOnError(error -> DataBufferUtils.release(buffer));
});
return mono;
};
}
/**
* @return
* @link https://blog.csdn.net/qq_24230139/article/details/105091273
* ServerHttpSecurity没有将jwt中authorities的负载部分当做Authentication
* 需要把jwt的Claim中的authorities加入
* 方案:重新定义R 权限管理器,默认转换器JwtGrantedAuthoritiesConverter
*/
@Bean
public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstants.JWT_AUTHORITIES_KEY);
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
}
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* 鉴权管理器
*/
@Component
@AllArgsConstructor
@Slf4j
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
private RedisTemplate redisTemplate;
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
ServerHttpRequest request = authorizationContext.getExchange().getRequest();
String path = request.getURI().getPath();
PathMatcher pathMatcher = new AntPathMatcher();
// 从缓存取资源权限角色关系列表
Map<Object, Object> permissionRoles = redisTemplate.opsForHash().entries(AuthConstants.PERMISSION_RULES_KEY);
Iterator<Object> iterator = permissionRoles.keySet().iterator();
// 请求路径匹配到的资源需要的角色权限集合authorities统计
Set<String> authorities = new HashSet<>();
while (iterator.hasNext()) {
String pattern = (String) iterator.next();
if (pathMatcher.match(pattern, path)) {
authorities.addAll(Convert.toList(String.class, permissionRoles.get(pattern)));
}
}
return mono
.filter(Authentication::isAuthenticated)
.flatMapIterable(Authentication::getAuthorities)
.map(GrantedAuthority::getAuthority)
.any(authorities::contains)
.map(AuthorizationDecision::new)
.defaultIfEmpty(new AuthorizationDecision(false));
}
}
/**
* 白名单配置
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "whitelist")
public class WhiteListConfig {
private List<String> urls;
}
配置
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: 'http://localhost:9067/getPublicKey'
white-list:
urls:
- "/auth/oauth/token"
- "/auth/phone/allCode"
- "/user/getUserDetail"
- "/auth/oauth/saveUser"
刷新token
密码模式
手机号模式
有什么建议可以私聊 也可以留言谢谢