新增授权码模式

This commit is contained in:
zhuyijun 2022-10-15 14:09:41 +08:00
parent bf24b36c62
commit ed9885bf85
35 changed files with 999 additions and 155 deletions

View File

@ -38,7 +38,6 @@
<feign-ribbon.version>11.8</feign-ribbon.version>
<!-- json解析 -->
<jackson.version>2.13.3</jackson.version>
<jackson-datatype-jsr310.version>2.13.3</jackson-datatype-jsr310.version>
<fastjson.version>2.0.11</fastjson.version>
<!-- ORM -->
<mybatis-plus-boot-starter.version>3.4.3</mybatis-plus-boot-starter.version>
@ -68,7 +67,8 @@
<commons-crypto.version>1.1.0</commons-crypto.version>
<!-- 文档-->
<knife4j.version>3.0.3</knife4j.version>
<!--编译编码-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
@ -170,11 +170,6 @@
<artifactId>nacos-client</artifactId>
<version>${nacos-client.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson-datatype-jsr310.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>

View File

@ -0,0 +1,75 @@
package cn.zyjblogs.config.redis;
import cn.zyjblogs.config.redis.serializer.OAuth2AccessTokenMixIn;
import cn.zyjblogs.config.redis.serializer.OAuth2AuthenticationMixin;
import cn.zyjblogs.starter.redis.config.GenericJackson2JsonRedisSerializerEx;
import cn.zyjblogs.starter.redis.config.RedisConfiguration;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.security.jackson2.CoreJackson2Module;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.web.jackson2.WebJackson2Module;
/**
* RedisConfig
*
* @author zhuyijun
*/
@Configuration(
proxyBeanMethods = false
)
@AutoConfigureAfter(RedisConfiguration.class)
public class RedisConfig {
/**
* Redis配置
* 针对keyvaluehashKeyhashValue做序列化
*
* @return org.springframework.data.redis.core.RedisTemplate<java.lang.String, java.lang.Object>
*/
@Bean
@SuppressWarnings("all")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
ObjectMapper om = new ObjectMapper();
om.registerModule(new JavaTimeModule());
om.registerModule(new CoreJackson2Module());
om.registerModule(new WebJackson2Module());
om.addMixIn(OAuth2AccessToken.class, OAuth2AccessTokenMixIn.class);
om.addMixIn(OAuth2Authentication.class, OAuth2AuthenticationMixin.class);
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 序列化时添加类型否则反序列化时会报错无法转换
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
// 反序列化时候遇到不匹配的属性并不抛出异常 忽略json字符串中不识别的属性
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 序列化时候遇到空对象不抛出异常 忽略无法转换的对象 No serializer found for class com.xxx.xxx
om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
GenericJackson2JsonRedisSerializerEx genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializerEx(om);
//序列号key value
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

View File

@ -0,0 +1,61 @@
package cn.zyjblogs.config.redis.serializer;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public final class OAuth2AccessTokenJackson2Deserializer extends StdDeserializer<OAuth2AccessToken> {
Logger logger = LoggerFactory.getLogger(OAuth2AccessTokenJackson2Deserializer.class);
private static final long serialVersionUID = 1L;
public OAuth2AccessTokenJackson2Deserializer() {
super(OAuth2AccessToken.class);
}
@Override
public OAuth2AccessToken deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
Map<String, Object> additionalInformation = new LinkedHashMap<String, Object>();
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode jsonNode = mapper.readTree(jp);
String tokenValue = jsonNode.get(OAuth2AccessToken.ACCESS_TOKEN).asText();
String tokenType = jsonNode.get(OAuth2AccessToken.TOKEN_TYPE).asText();
Integer expiresIn = jsonNode.get(OAuth2AccessToken.EXPIRES_IN).asInt();
String refreshToken = jsonNode.get(OAuth2AccessToken.REFRESH_TOKEN) == null ? null
: jsonNode.get(OAuth2AccessToken.REFRESH_TOKEN).asText();
String scopeText = jsonNode.get(OAuth2AccessToken.SCOPE).asText();
Set<String> scope = OAuth2Utils.parseParameterList(scopeText);
// TODO What should occur if a required parameter (tokenValue or tokenType) is missing?
DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(tokenValue);
accessToken.setTokenType(tokenType);
if (expiresIn != null) {
accessToken.setExpiration(new Date(System.currentTimeMillis() + (expiresIn * 1000)));
}
if (refreshToken != null) {
accessToken.setRefreshToken(new DefaultOAuth2RefreshToken(refreshToken));
}
accessToken.setScope(scope);
accessToken.setAdditionalInformation(additionalInformation);
return accessToken;
}
}

View File

@ -0,0 +1,72 @@
package cn.zyjblogs.config.redis.serializer;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.util.Assert;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
public class OAuth2AccessTokenJackson2Serializer extends StdSerializer<OAuth2AccessToken> {
private static final long serialVersionUID = 1L;
public OAuth2AccessTokenJackson2Serializer() {
super(OAuth2AccessToken.class);
}
@Override
public void serializeWithType(OAuth2AccessToken value, JsonGenerator gen, SerializerProvider serializers,
TypeSerializer typeSer) throws IOException {
// TODO Auto-generated method stub
extracted(value, gen, serializers, typeSer);
}
@Override
public void serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerProvider serializers)
throws IOException, JsonGenerationException {
extracted(token, jgen, serializers, null);
}
private void extracted(OAuth2AccessToken token, JsonGenerator jgen, SerializerProvider serializers,
TypeSerializer typeSer) throws IOException {
jgen.writeStartObject();
if (typeSer != null) {
jgen.writeStringField(typeSer.getPropertyName(), token.getClass().getName());
}
jgen.writeStringField(OAuth2AccessToken.ACCESS_TOKEN, token.getValue());
jgen.writeStringField(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType());
OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken != null) {
jgen.writeStringField(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue());
}
Date expiration = token.getExpiration();
if (expiration != null) {
long now = System.currentTimeMillis();
jgen.writeNumberField(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000);
}
Set<String> scope = token.getScope();
if (scope != null && !scope.isEmpty()) {
StringBuffer scopes = new StringBuffer();
for (String s : scope) {
Assert.hasLength(s, "Scopes cannot be null or empty. Got " + scope + "");
scopes.append(s);
scopes.append(" ");
}
jgen.writeStringField(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1));
}
Map<String, Object> additionalInformation = token.getAdditionalInformation();
for (String key : additionalInformation.keySet()) {
jgen.writeObjectField(key, additionalInformation.get(key));
}
jgen.writeEndObject();
}
}

View File

@ -0,0 +1,9 @@
package cn.zyjblogs.config.redis.serializer;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class)
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class)
public class OAuth2AccessTokenMixIn {
}

View File

@ -0,0 +1,182 @@
package cn.zyjblogs.config.redis.serializer;
import cn.zyjblogs.server.user.po.OauthUserDetails;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.node.MissingNode;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class OAuth2AuthenticationJackson2Deserializer extends StdDeserializer<OAuth2Authentication> {
protected OAuth2AuthenticationJackson2Deserializer() {
super(OAuth2Authentication.class);
}
public OAuth2AuthenticationJackson2Deserializer(Class<?> vc) {
super(vc);
}
private static String readString(ObjectMapper mapper, JsonNode jsonNode) {
return readValue(mapper, jsonNode, String.class);
}
private static <T> T readValue(ObjectMapper mapper, JsonNode jsonNode, Class<T> clazz) {
if (mapper == null || jsonNode == null || clazz == null) {
return null;
}
try {
return mapper.readValue(jsonNode.traverse(), clazz);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static <T> T readValue(ObjectMapper mapper, JsonNode jsonNode, TypeReference<T> type) {
if (mapper == null || jsonNode == null || type == null) {
return null;
}
try {
return mapper.readValue(jsonNode.traverse(), type);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public OAuth2Authentication deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode jsonNode = mapper.readTree(jp);
JsonNode requestNode = readJsonNode(jsonNode, "oauth2Request");
JsonNode userAuthenticationNode = readJsonNode(jsonNode, "userAuthentication");
Authentication authentication = parseAuthentication(mapper, userAuthenticationNode);
OAuth2Request request = parseOAuth2Request(mapper, requestNode);
return new OAuth2Authentication(request, authentication);
}
/**
* 解析Authentication
*
* @param mapper
* @param json
* @return
* @author zhuyijun
* @date 2022/10/15
*/
private Authentication parseAuthentication(ObjectMapper mapper, JsonNode json) {
if (mapper == null || json == null) {
return null;
}
OauthUserDetails principal = parseOAuth2User(mapper, json.get("principal"));
Object credentials = readValue(mapper, json.get("credentials"), Object.class);
Collection<SimpleGrantedAuthority> grantedAuthorities = parseSimpleGrantedAuthorities(mapper, json.get("authorities"));
return new UsernamePasswordAuthenticationToken(principal, credentials, grantedAuthorities);
}
private OauthUserDetails parseOAuth2User(ObjectMapper mapper, JsonNode json) {
if (mapper == null || json == null) {
return null;
}
String id = readString(mapper, json.get("id"));
String username = readString(mapper, json.get("username"));
String password = readString(mapper, json.get("password"));
String tenantId = readString(mapper, json.get("tenantId"));
String name = readString(mapper, json.get("name"));
String phone = readString(mapper, json.get("phone"));
String email = readString(mapper, json.get("email"));
Boolean accountNonExpired = readValue(mapper, json.get("accountNonExpired"), Boolean.class);
Boolean accountNonLocked = readValue(mapper, json.get("accountNonLocked"), Boolean.class);
Boolean credentialsNonExpired = readValue(mapper, json.get("credentialsNonExpired"), Boolean.class);
Boolean enabled = readValue(mapper, json.get("enabled"), Boolean.class);
Collection<SimpleGrantedAuthority> grantedAuthorities = parseSimpleGrantedAuthorities(mapper, json.get("authorities"));
return OauthUserDetails.builder()
.id(id)
.username(username)
.password(password)
.name(name)
.phone(phone)
.email(email)
.tenantId(tenantId)
.accountNonExpired(accountNonExpired)
.accountNonLocked(accountNonLocked)
.credentialsNonExpired(credentialsNonExpired)
.enabled(enabled)
.authorities(grantedAuthorities)
.build();
}
private OAuth2Request parseOAuth2Request(ObjectMapper mapper, JsonNode json) {
if (mapper == null || json == null) {
return null;
}
Map<String, String> requestParameters = readValue(mapper, json.get("requestParameters"), Collections.unmodifiableMap(new HashMap<String, String>(0)).getClass());
String clientId = readString(mapper, json.get("clientId"));
String grantType = readString(mapper, json.get("grantType"));
String redirectUri = readString(mapper, json.get("redirectUri"));
Boolean approved = readValue(mapper, json.get("approved"), Boolean.class);
Set<String> responseTypes = readValue(mapper, json.get("responseTypes"), Set.class);
String scope1 = requestParameters.get("scope");
Set<String> scope = StringUtils.hasLength(scope1) ? Set.of(scope1.split(",")) : new HashSet<>();
Set<String> resourceIds = readValue(mapper, json.get("resourceIds"), Set.class);
Map<String, Serializable> extensions = readValue(mapper, json.get("extensions"), new TypeReference<>() {
});
Collection<SimpleGrantedAuthority> grantedAuthorities = parseSimpleGrantedAuthorities(mapper, json.get("authorities"));
OAuth2Request request = new OAuth2Request(requestParameters, clientId,
grantedAuthorities, approved, scope, resourceIds, redirectUri, responseTypes, extensions);
TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scope, grantType);
request.refresh(tokenRequest);
return request;
}
/**
* 处理权限
*
* @param mapper
* @param json
* @return
* @author zhuyijun
* @date 2022/10/15
*/
private Collection<SimpleGrantedAuthority> parseSimpleGrantedAuthorities(ObjectMapper mapper, JsonNode json) {
Collection<LinkedHashMap<String, String>> authorities = readValue(mapper, json, Collection.class);
Collection<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<>();
if (authorities != null && !authorities.isEmpty()) {
authorities.forEach(s -> {
if (s != null && !s.isEmpty()) {
grantedAuthorities.add(new SimpleGrantedAuthority(s.get("authority")));
}
});
}
return grantedAuthorities;
}
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
}
}

View File

@ -0,0 +1,14 @@
package cn.zyjblogs.config.redis.serializer;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
/**
* OAuth2Authentication混入类
*
* @author zhuyijun
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = OAuth2AuthenticationJackson2Deserializer.class)
public abstract class OAuth2AuthenticationMixin {
}

View File

@ -1,5 +1,7 @@
package cn.zyjblogs.config.security;
import cn.zyjblogs.config.security.policy.OauthAuthorizationCodeServices;
import cn.zyjblogs.starter.redis.utils.RedisTemplateHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -11,8 +13,8 @@ import org.springframework.security.oauth2.config.annotation.web.configuration.E
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
@ -39,6 +41,8 @@ public class AuthorizationServerConfiguration extends AuthorizationServerConfigu
private final JwtTokenEnhancer jwtTokenEnhancer;
private final OauthResponseExceptionTranslator oAuthResponseExceptionTranslator;
private final ClientDetailsService oauthClientDetailsService;
private final RedisTemplateHandler redisTemplateHandler;
/**
* 令牌端点的安全约束
*
@ -120,15 +124,15 @@ public class AuthorizationServerConfiguration extends AuthorizationServerConfigu
@Bean
public ClientDetailsService clientDetails(DataSource dataSource) {
// JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
// jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
return oauthClientDetailsService;
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
return jdbcClientDetailsService;
}
@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
//设置授权码模式的授权码如何存取
return new JdbcAuthorizationCodeServices(dataSource);
return new OauthAuthorizationCodeServices(redisTemplateHandler);
// return new JdbcAuthorizationCodeServices(dataSource);
}
}

View File

@ -24,7 +24,8 @@ public class OauthAccessTokenConverter extends DefaultAccessTokenConverter {
String userId = (String) map.get(ContextKeyConstant.USER_ID_KEY);
String username = (String) map.get(ContextKeyConstant.USERNAME_KEY);
String tenantId = (String) map.get(ContextKeyConstant.TENANT_ID_KEY);
BaseContext.set(ContextDto.builder().userId(userId).username(username).token(value).tenantId(tenantId).build());
String name = (String) map.get(ContextKeyConstant.NAME_KEY);
BaseContext.set(ContextDto.builder().userId(userId).username(username).token(value).name(name).tenantId(tenantId).build());
return super.extractAccessToken(value, map);
}

View File

@ -51,14 +51,15 @@ public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
.csrf()
.disable()
//限制资源服务器作用范围为 "/user/**", "/demo/**"
.requestMatchers().antMatchers("/v*/**,/webjars/**", "/swagger-ui.html/**", "/swagger-resources/**", "/v2/api-docs/**", "/v*/user/**", "/demo/**",
.requestMatchers().antMatchers("/v*/**", "/demo/**",
String.join(",", whiteListProperties.getAllowPaths()))
.and()
.formLogin().and()
.formLogin()
.and()
.authorizeRequests()
.antMatchers("/webjars/**", "/swagger-ui.html/**", "/swagger-resources/**", "/v2/api-docs/**", String.join(",", whiteListProperties.getAllowPaths()))
.antMatchers(String.join(",", whiteListProperties.getAllowPaths()))
.permitAll()
.antMatchers("/login", "/oauth/**", "/v*/user/login", "/v*/auth/refresh/token").permitAll()
.antMatchers("/v*/user/login", "/v*/auth/refresh/token").permitAll()
//以下请求必须认证通过
.anyRequest()
.authenticated().and()

View File

@ -61,6 +61,7 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
.and()
//允许表单登录
.formLogin()
// .loginPage("/login").loginProcessingUrl("/v1/user/login")
.and()
.httpBasic();
}

View File

@ -0,0 +1,54 @@
package cn.zyjblogs.config.security.policy;
import cn.zyjblogs.starter.common.entity.constant.CommonRedisKeyConstant;
import cn.zyjblogs.starter.redis.utils.RedisTemplateHandler;
import lombok.SneakyThrows;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy;
/**
* @author zhuyijun
*/
public class OauthAuthorizationCodeServices implements AuthorizationCodeServices {
private final RedisTemplateHandler<String, OAuth2Authentication> redisTemplateHandler;
public OauthAuthorizationCodeServices(RedisTemplateHandler redisTemplateHandler) {
this.redisTemplateHandler = redisTemplateHandler;
}
/**
* 生成随机字符的类
*/
private RandomValueStringGenerator generator = new RandomValueStringGenerator(10);
private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();
/**
* @description 生成授权码的方法
* @param: [oAuth2Authentication]
* @return: java.lang.String
*/
@SneakyThrows
@Override
public String createAuthorizationCode(OAuth2Authentication oAuth2Authentication) {
String code = this.generator.generate();
redisTemplateHandler.hPut(CommonRedisKeyConstant.AUTHORIZATION_CODE, code, oAuth2Authentication);
return code;
}
/**
* @description
* @param: [code]
* @return: org.springframework.security.oauth2.provider.OAuth2Authentication
*/
@SneakyThrows
@Override
public OAuth2Authentication consumeAuthorizationCode(String code) throws InvalidGrantException {
return redisTemplateHandler.<String, OAuth2Authentication>hGet(CommonRedisKeyConstant.AUTHORIZATION_CODE, code);
}
}

View File

@ -44,8 +44,8 @@ public class AuthController {
@ApiOperation(value = "获取授权码", notes = "刷新token")
@PostMapping("/authorize")
@ApiVersion(1)
public void getAuthorizationCode(@RequestBody @Validated AuthorizationCodeDto authorizationCodeDto) {
authService.getAuthorizationCode(authorizationCodeDto);
public ResponseObject getAuthorizationCode(@RequestBody @Validated AuthorizationCodeDto authorizationCodeDto) {
return ResponseResult.success(authService.getAuthorizationCode(authorizationCodeDto));
}
}

View File

@ -16,4 +16,9 @@ public class AuthorizationCodeDto {
private String clientId;
@ApiModelProperty(value = "客户端密码", dataType = "String", example = "all")
private String clientSecret;
@ApiModelProperty(value = "返回类型 code token", dataType = "String", example = "all")
private String responseType;
private String state;
private Boolean isApproved;
}

View File

@ -0,0 +1,101 @@
package cn.zyjblogs.server.user.dto;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.Collection;
import java.util.Collections;
/**
* @author zhuyijun
*/
public class AuthorizationDto implements Authentication {
private String id;
private String name;
private String username;
private Object principal;
private String credentials;
private Collection<SimpleGrantedAuthority> authorities;
private Object details;
private Boolean authenticated;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities == null ? Collections.emptyList() : authorities;
}
@Override
public Object getCredentials() {
return credentials;
}
@Override
public Object getDetails() {
return details;
}
@Override
public Object getPrincipal() {
return principal;
}
@Override
public boolean isAuthenticated() {
return authenticated;
}
@Override
public void setAuthenticated(boolean b) throws IllegalArgumentException {
this.authenticated = b;
}
@Override
public String getName() {
return name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public void setPrincipal(Object principal) {
this.principal = principal;
}
public void setCredentials(String credentials) {
this.credentials = credentials;
}
public void setAuthorities(Collection<SimpleGrantedAuthority> authorities) {
this.authorities = authorities;
}
public void setDetails(Object details) {
this.details = details;
}
public Boolean getAuthenticated() {
return authenticated;
}
public void setAuthenticated(Boolean authenticated) {
this.authenticated = authenticated;
}
}

View File

@ -8,6 +8,7 @@ import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
@ -40,7 +41,7 @@ public class OauthUserDetails implements UserDetails, Serializable {
private Integer deleted;
private String tenantId;
private Collection<Role> authorities;
private Collection<SimpleGrantedAuthority> authorities;
private boolean accountNonExpired = true;
private boolean accountNonLocked = true;
private boolean credentialsNonExpired = true;
@ -118,7 +119,7 @@ public class OauthUserDetails implements UserDetails, Serializable {
this.deleted = deleted;
}
public void setAuthorities(Collection<Role> authorities) {
public void setAuthorities(Collection<SimpleGrantedAuthority> authorities) {
this.authorities = authorities;
}

View File

@ -29,5 +29,5 @@ public interface AuthService {
*
* @param authorizationCodeDto
*/
void getAuthorizationCode(AuthorizationCodeDto authorizationCodeDto);
String getAuthorizationCode(AuthorizationCodeDto authorizationCodeDto);
}

View File

@ -1,25 +1,52 @@
package cn.zyjblogs.server.user.service.impl;
import cn.zyjblogs.server.user.dto.AuthorizationCodeDto;
import cn.zyjblogs.server.user.dto.AuthorizationDto;
import cn.zyjblogs.server.user.dto.OAuth2AccessTokenDto;
import cn.zyjblogs.server.user.po.OauthUserDetails;
import cn.zyjblogs.server.user.service.AuthService;
import cn.zyjblogs.server.user.vo.OAuth2AccessTokenVo;
import cn.zyjblogs.starter.common.entity.context.BaseContext;
import cn.zyjblogs.starter.common.entity.response.HttpCode;
import cn.zyjblogs.starter.common.exception.AuthRuntimeException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.common.exceptions.UnsupportedResponseTypeException;
import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
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.TokenGranter;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenRequest;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* @author zhuyijun
@ -29,13 +56,18 @@ public class AuthServiceImpl implements AuthService {
private final TokenGranter tokenGranter;
private final ClientDetailsService clientDetails;
private final OAuth2RequestFactory oAuth2RequestFactory;
private final AuthorizationCodeServices authorizationCodeServices;
private final PasswordEncoder passwordEncoder;
private final Object implicitLock = new Object();
public AuthServiceImpl(AuthorizationServerEndpointsConfiguration authorizationServerEndpointsConfiguration,
ClientDetailsService clientDetails,
TokenStore tokenStore) {
TokenStore tokenStore, AuthorizationCodeServices authorizationCodeServices, PasswordEncoder passwordEncoder) {
this.tokenGranter = authorizationServerEndpointsConfiguration.getEndpointsConfigurer().getTokenGranter();
this.clientDetails = clientDetails;
this.oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetails);
this.authorizationCodeServices = authorizationCodeServices;
this.passwordEncoder = passwordEncoder;
}
@Value("${security.oauth2.client.client-id}")
@ -47,7 +79,7 @@ public class AuthServiceImpl implements AuthService {
parameters.put("refresh_token", oAuth2AccessTokenDto.getToken());
parameters.put("grant_type", "refresh_token");
ClientDetails authenticatedClient = clientDetails.loadClientByClientId(clientId);
if (authenticatedClient == null){
if (authenticatedClient == null) {
throw new AuthRuntimeException(HttpCode.INTERNAL_SERVER_ERROR, "客户端获取失败");
}
try {
@ -68,19 +100,250 @@ public class AuthServiceImpl implements AuthService {
}
@Override
public void getAuthorizationCode(AuthorizationCodeDto authorizationCodeDto) {
// Map<String, String> parameters = new HashMap<>();
// parameters.put("client_id", authorizationCodeDto.getClientId());
// parameters.put("client_secret", authorizationCodeDto.getClientSecret());
// parameters.put("redirect_url", authorizationCodeDto.getRedirectUrl());
// parameters.put("response_type", "code");
// parameters.put("scope", authorizationCodeDto.getScope());
// AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(parameters);
// try {
// boolean approved = authorizationRequest.isApproved();
// } catch (Exception e) {
// throw new AuthRuntimeException(HttpCode.INTERNAL_SERVER_ERROR, e.getMessage());
public String getAuthorizationCode(AuthorizationCodeDto authorizationCodeDto) {
String responseType = authorizationCodeDto.getResponseType();
if (!StringUtils.hasLength(responseType)) {
throw new AuthRuntimeException(HttpCode.BAD_REQUEST, "responseType不能为空");
}
Set<String> responseTypes = Set.of(responseType.split(","));
if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
}
ClientDetails clientDetail = this.clientDetails.loadClientByClientId(authorizationCodeDto.getClientId());
if (clientDetail == null) {
throw new AuthRuntimeException(HttpCode.BAD_REQUEST, "无此客户端");
}
// if (!passwordEncoder.matches(clientDetail.getClientSecret(), authorizationCodeDto.getClientSecret())) {
// throw new AuthRuntimeException(HttpCode.UNAUTHORIZED, "客户端" + authorizationCodeDto.getClientId() + "认证失败");
// }
Map<String, String> parameters = new HashMap<>();
parameters.put(OAuth2Utils.CLIENT_ID, authorizationCodeDto.getClientId());
parameters.put("client_secret", authorizationCodeDto.getClientSecret());
parameters.put(OAuth2Utils.REDIRECT_URI, authorizationCodeDto.getRedirectUrl());
parameters.put(OAuth2Utils.RESPONSE_TYPE, "code");
parameters.put(OAuth2Utils.SCOPE, authorizationCodeDto.getScope());
parameters.put(OAuth2Utils.STATE, authorizationCodeDto.getState());
AuthorizationRequest authorizationRequest = createAuthorizationRequest(parameters, clientDetail);
if (!authorizationCodeDto.getIsApproved()) {
return getUnsuccessfulRedirect(authorizationRequest,
new UserDeniedAuthorizationException("User denied access"), false);
}
if (responseTypes.contains("token")) {
return getImplicitGrantResponse(authorizationRequest);
}
try {
AuthorizationDto authorizationDto = new AuthorizationDto();
OauthUserDetails oauthUserDetails = new OauthUserDetails();
oauthUserDetails.setId(BaseContext.getUserId());
oauthUserDetails.setName(BaseContext.getName());
oauthUserDetails.setUsername(BaseContext.getUsername());
oauthUserDetails.setTenantId(BaseContext.getTenantId());
authorizationDto.setPrincipal(oauthUserDetails);
authorizationDto.setAuthenticated(true);
return getSuccessfulRedirect(authorizationRequest,
generateCode(authorizationRequest, authorizationDto));
} catch (OAuth2Exception e) {
return getUnsuccessfulRedirect(authorizationRequest, e, false);
}
}
/**
* 处理
*
* @param authorizationParameters
* @param clientDetails
* @return
*/
public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters, ClientDetails clientDetails) {
String clientId = authorizationParameters.get(OAuth2Utils.CLIENT_ID);
String state = authorizationParameters.get(OAuth2Utils.STATE);
String redirectUri = authorizationParameters.get(OAuth2Utils.REDIRECT_URI);
Set<String> responseTypes = OAuth2Utils.parseParameterList(authorizationParameters
.get(OAuth2Utils.RESPONSE_TYPE));
Set<String> scopes = OAuth2Utils.parseParameterList(authorizationParameters.get(OAuth2Utils.SCOPE));
if (CollectionUtils.isEmpty(scopes)) {
scopes = clientDetails.getScope();
}
AuthorizationRequest request = new AuthorizationRequest(authorizationParameters,
Collections.emptyMap(), clientId, scopes, null, null, false, state, redirectUri,
responseTypes);
request.setResourceIdsAndAuthoritiesFromClientDetails(clientDetails);
return request;
}
private String appendAccessToken(AuthorizationRequest authorizationRequest, OAuth2AccessToken accessToken) {
Map<String, Object> vars = new LinkedHashMap<String, Object>();
Map<String, String> keys = new HashMap<String, String>();
if (accessToken == null) {
throw new InvalidRequestException("An implicit grant could not be made");
}
vars.put("access_token", accessToken.getValue());
vars.put("refresh_token", accessToken.getRefreshToken());
vars.put("token_type", accessToken.getTokenType());
vars.put("expiration", accessToken.getExpiration());
String state = authorizationRequest.getState();
if (state != null) {
vars.put("state", state);
}
Date expiration = accessToken.getExpiration();
if (expiration != null) {
long expires_in = (expiration.getTime() - System.currentTimeMillis()) / 1000;
vars.put("expires_in", expires_in);
}
String originalScope = authorizationRequest.getRequestParameters().get(OAuth2Utils.SCOPE);
if (originalScope == null || !OAuth2Utils.parseParameterList(originalScope).equals(accessToken.getScope())) {
vars.put("scope", OAuth2Utils.formatParameterList(accessToken.getScope()));
}
Map<String, Object> additionalInformation = accessToken.getAdditionalInformation();
for (String key : additionalInformation.keySet()) {
Object value = additionalInformation.get(key);
if (value != null) {
keys.put("extra_" + key, key);
vars.put("extra_" + key, value);
}
}
// Do not include the refresh token (even if there is one)
return append(authorizationRequest.getRedirectUri(), vars, keys, true);
}
private String getImplicitGrantResponse(AuthorizationRequest authorizationRequest) {
try {
TokenRequest tokenRequest = oAuth2RequestFactory.createTokenRequest(authorizationRequest, "implicit");
OAuth2Request storedOAuth2Request = oAuth2RequestFactory.createOAuth2Request(authorizationRequest);
OAuth2AccessToken accessToken = getAccessTokenForImplicitGrant(tokenRequest, storedOAuth2Request);
if (accessToken == null) {
throw new UnsupportedResponseTypeException("Unsupported response type: token");
}
return appendAccessToken(authorizationRequest, accessToken);
} catch (OAuth2Exception e) {
return getUnsuccessfulRedirect(authorizationRequest, e, true);
}
}
private OAuth2AccessToken getAccessTokenForImplicitGrant(TokenRequest tokenRequest,
OAuth2Request storedOAuth2Request) {
OAuth2AccessToken accessToken = null;
// These 1 method calls have to be atomic, otherwise the ImplicitGrantService can have a race condition where
// one thread removes the token request before another has a chance to redeem it.
synchronized (this.implicitLock) {
accessToken = tokenGranter.grant("implicit",
new ImplicitTokenRequest(tokenRequest, storedOAuth2Request));
}
return accessToken;
}
private String generateCode(AuthorizationRequest authorizationRequest, Authentication authentication) throws AuthenticationException {
try {
OAuth2Request storedOAuth2Request = this.oAuth2RequestFactory.createOAuth2Request(authorizationRequest);
OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication);
return this.authorizationCodeServices.createAuthorizationCode(combinedAuth);
} catch (OAuth2Exception e) {
if (authorizationRequest.getState() != null) {
e.addAdditionalInformation("state", authorizationRequest.getState());
}
throw e;
}
}
private String append(String base, Map<String, ?> query, Map<String, String> keys, boolean fragment) {
UriComponentsBuilder template = UriComponentsBuilder.newInstance();
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base);
URI redirectUri;
try {
// assume it's encoded to start with (if it came in over the wire)
redirectUri = builder.build(true).toUri();
} catch (Exception e) {
// ... but allow client registrations to contain hard-coded non-encoded values
redirectUri = builder.build().toUri();
builder = UriComponentsBuilder.fromUri(redirectUri);
}
template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost())
.userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());
if (fragment) {
StringBuilder values = new StringBuilder();
if (redirectUri.getFragment() != null) {
String append = redirectUri.getFragment();
values.append(append);
}
for (String key : query.keySet()) {
if (values.length() > 0) {
values.append("&");
}
String name = key;
if (keys != null && keys.containsKey(key)) {
name = keys.get(key);
}
values.append(name + "={" + key + "}");
}
if (values.length() > 0) {
template.fragment(values.toString());
}
UriComponents encoded = template.build().expand(query).encode();
builder.fragment(encoded.getFragment());
} else {
for (String key : query.keySet()) {
String name = key;
if (keys != null && keys.containsKey(key)) {
name = keys.get(key);
}
template.queryParam(name, "{" + key + "}");
}
template.fragment(redirectUri.getFragment());
UriComponents encoded = template.build().expand(query).encode();
builder.query(encoded.getQuery());
}
return builder.build().toUriString();
}
private String append(String base, Map<String, ?> query, boolean fragment) {
return this.append(base, query, (Map) null, fragment);
}
private String getSuccessfulRedirect(AuthorizationRequest authorizationRequest, String authorizationCode) {
if (authorizationCode == null) {
throw new IllegalStateException("No authorization code found in the current request scope.");
} else {
Map<String, String> query = new LinkedHashMap();
query.put("code", authorizationCode);
String state = authorizationRequest.getState();
if (state != null) {
query.put("state", state);
}
return this.append(authorizationRequest.getRedirectUri(), query, false);
}
}
private String getUnsuccessfulRedirect(AuthorizationRequest authorizationRequest, OAuth2Exception failure, boolean fragment) {
if (authorizationRequest != null && authorizationRequest.getRedirectUri() != null) {
Map<String, String> query = new LinkedHashMap();
query.put("error", failure.getOAuth2ErrorCode());
query.put("error_description", failure.getMessage());
if (authorizationRequest.getState() != null) {
query.put("state", authorizationRequest.getState());
}
if (failure.getAdditionalInformation() != null) {
Iterator var5 = failure.getAdditionalInformation().entrySet().iterator();
while (var5.hasNext()) {
Map.Entry<String, String> additionalInfo = (Map.Entry) var5.next();
query.put(additionalInfo.getKey(), additionalInfo.getValue());
}
}
return this.append(authorizationRequest.getRedirectUri(), query, fragment);
} else {
throw new UnapprovedClientAuthenticationException("Authorization failure, and no redirect URI.", failure);
}
}
}

View File

@ -17,6 +17,8 @@
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<!--编译编码-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>

View File

@ -4,6 +4,13 @@ package cn.zyjblogs.starter.common.entity.constant;
* @author zhuyijun
*/
public class CommonRedisKeyConstant {
/**
* 授权码
*/
public final static String AUTHORIZATION_CODE = "OAUTH:AUTHORIZATION_CODE";
/**
* rsa
*/
public static final String REDIS_KEY_PRIVATE_RSA = "rsa:key:private_key";
public static final String REDIS_KEY_PUBLIC_RSA = "rsa:key:public_key";

View File

@ -30,8 +30,13 @@ public class BaseContext {
return CONTEXT.get().getUsername();
}
public static String getName() {
return CONTEXT.get().getName();
}
public static String getTenantId() {
return CONTEXT.get().getTenantId();
final String tenantId = CONTEXT.get().getTenantId();
return tenantId == null ? "" : tenantId;
}
public static String getToken() {

View File

@ -11,6 +11,7 @@ import lombok.NoArgsConstructor;
@Builder
public class ContextDto {
private String userId;
private String name;
private String username;
private String token;
private String tenantId;

View File

@ -17,6 +17,8 @@
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<!--编译编码-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 集成spring-boot自动装配依赖 -->

View File

@ -17,6 +17,8 @@
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<!--编译编码-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 集成spring-boot自动装配依赖 -->

View File

@ -17,6 +17,8 @@
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<!--编译编码-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>

View File

@ -3,8 +3,6 @@ package cn.zyjblogs.starter.oauth.token;
import cn.zyjblogs.starter.common.entity.constant.ContextKeyConstant;
import cn.zyjblogs.starter.common.entity.context.BaseContext;
import cn.zyjblogs.starter.common.entity.dto.ContextDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
@ -17,14 +15,14 @@ import java.util.Map;
*/
@Component
public class OauthAccessTokenConverter extends DefaultAccessTokenConverter {
private static final Logger log = LoggerFactory.getLogger(OauthAccessTokenConverter.class);
@Override
public OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map) {
String userId = (String) map.get(ContextKeyConstant.USER_ID_KEY);
String username = (String) map.get(ContextKeyConstant.USERNAME_KEY);
String tenantId = (String) map.get(ContextKeyConstant.TENANT_ID_KEY);
BaseContext.set(ContextDto.builder().userId(userId).username(username).token(value).tenantId(tenantId).build());
String name = (String) map.get(ContextKeyConstant.NAME_KEY);
BaseContext.set(ContextDto.builder().userId(userId).username(username).token(value).name(name).tenantId(tenantId).build());
return super.extractAccessToken(value, map);
}

View File

@ -18,6 +18,8 @@
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<!--编译编码-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>

View File

@ -18,6 +18,8 @@
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<!--编译编码-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 集成spring-boot自动装配依赖 -->

View File

@ -1,60 +1,64 @@
package cn.zyjblogs.starter.redis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.cache.support.NullValue;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.io.IOException;
/**
* @author zhuyijun
*/
public class GenericJackson2JsonRedisSerializerEx implements RedisSerializer<Object> {
protected GenericJackson2JsonRedisSerializer serializer = null;
public GenericJackson2JsonRedisSerializerEx() {
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.registerModule(new JavaTimeModule());
this.serializer = new GenericJackson2JsonRedisSerializer(om);
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 反序列化时候遇到不匹配的属性并不抛出异常 忽略json字符串中不识别的属性
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 序列化时候遇到空对象不抛出异常 忽略无法转换的对象 No serializer found for class com.xxx.xxx
om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
serializer = new GenericJackson2JsonRedisSerializer(om);
}
public GenericJackson2JsonRedisSerializerEx(ObjectMapper om) {
this.serializer = new GenericJackson2JsonRedisSerializer(om);
}
@Override
public byte[] serialize(Object o) throws SerializationException {
return serializer.serialize(o);
}
@Override
public Object deserialize(byte[] bytes) throws SerializationException {
return serializer.deserialize(bytes);
}
protected class NullValueSerializer extends StdSerializer<NullValue> {
private static final long serialVersionUID = 1999052150548658807L;
private final String classIdentifier="@class";
private final String classIdentifier = "@class";
NullValueSerializer() {
super(NullValue.class);
}
@Override
public void serialize(NullValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeStartObject();

View File

@ -1,12 +1,5 @@
package cn.zyjblogs.starter.redis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
@ -14,37 +7,35 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* RedisConfig
*
* @author zhuyijun
*/
@Configuration
@Configuration(
proxyBeanMethods = false
)
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {
public class RedisConfiguration {
/**
* Redis配置
* 针对keyvaluehashKeyhashValue做序列化
* @return org.springframework.data.redis.core.RedisTemplate<java.lang.String,java.lang.Object>
* Redis配置
* 针对keyvaluehashKeyhashValue做序列化
*
* @return org.springframework.data.redis.core.RedisTemplate<java.lang.String, java.lang.Object>
*/
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@SuppressWarnings("all")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// //解决查询缓存转换异常的问题
// ObjectMapper om = new ObjectMapper();
// om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// //注册java时间处理模块
// om.registerModule(new JavaTimeModule());
// GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om);
GenericJackson2JsonRedisSerializerEx genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializerEx();
//序列号key value
@ -52,7 +43,6 @@ public class RedisConfig {
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

View File

@ -20,54 +20,60 @@ import java.util.concurrent.TimeUnit;
@Component
@Lazy
public class RedisTemplateHandler<K,V> {
public class RedisTemplateHandler<K, V> {
@Resource
@Lazy
private RedisTemplate<K,V> redisTemplate;
private RedisTemplate<K, V> redisTemplate;
public void set(K key,V value){
redisTemplate.opsForValue().set(key,value);
public void set(K key, V value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* @param key
* @param value
* @param key
* @param value
* @param l
* @param timeUnit
* @author zhuyijun
* @date 2021/12/16 14:33
* @return void
*/
public void set(K key,V value,long l,TimeUnit timeUnit){
redisTemplate.opsForValue().set(key,value,l,timeUnit);
* @return void
* @author zhuyijun
* @date 2021/12/16 14:33
*/
public void set(K key, V value, long l, TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, l, timeUnit);
}
public V get(K key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 删除key
*
* @param key key
* @author zhuyijun
* @date 2021/12/16 15:45
* @return void
*/
* @return void
* @author zhuyijun
* @date 2021/12/16 15:45
*/
public void delete(K key) {
redisTemplate.delete(key);
}
/**
* 通过前缀删除
*
* @param key
*/
public void deleteByPrefix(K key){
public void deleteByPrefix(K key) {
Set<K> keys = redisTemplate.keys(key);
if (!CollectionUtils.isEmpty(keys)){
if (!CollectionUtils.isEmpty(keys)) {
redisTemplate.delete(keys);
}
}
/**
* 批量删除key
*
* @param keys
*/
public void delete(Collection<K> keys) {
@ -76,6 +82,7 @@ public class RedisTemplateHandler<K,V> {
/**
* 序列化key
*
* @param key
* @return
*/
@ -85,6 +92,7 @@ public class RedisTemplateHandler<K,V> {
/**
* 是否存在key
*
* @param key
* @return
*/
@ -166,6 +174,7 @@ public class RedisTemplateHandler<K,V> {
public Long getExpire(K key) {
return redisTemplate.getExpire(key);
}
/**
* 返回 key 所储存的值的类型
*
@ -175,6 +184,7 @@ public class RedisTemplateHandler<K,V> {
public DataType type(K key) {
return redisTemplate.type(key);
}
/**
* 获取存储在哈希表中指定字段的值
*
@ -208,10 +218,10 @@ public class RedisTemplateHandler<K,V> {
return redisTemplate.<HK, HV>opsForHash().multiGet(key, fields);
}
public <HK,HV> boolean hPut(K key, HK hashKey, HV value) {
public <HK, HV> boolean hPut(K key, HK hashKey, HV value) {
try {
if (value != null) {
this.redisTemplate.<HK,HV>opsForHash().put(key, hashKey, value);
this.redisTemplate.<HK, HV>opsForHash().put(key, hashKey, value);
return true;
} else {
return false;
@ -234,8 +244,8 @@ public class RedisTemplateHandler<K,V> {
* @param value
* @return
*/
public <HK,HV> Boolean hPutIfAbsent(K key, HK hashKey, HV value) {
return redisTemplate.<HK,HV>opsForHash().putIfAbsent(key, hashKey, value);
public <HK, HV> Boolean hPutIfAbsent(K key, HK hashKey, HV value) {
return redisTemplate.<HK, HV>opsForHash().putIfAbsent(key, hashKey, value);
}
/**
@ -256,8 +266,8 @@ public class RedisTemplateHandler<K,V> {
* @param field
* @return
*/
public <HK,HV> boolean hExists(K key, HV field) {
return redisTemplate.<HK,HV>opsForHash().hasKey(key, field);
public <HK, HV> boolean hExists(K key, HV field) {
return redisTemplate.<HK, HV>opsForHash().hasKey(key, field);
}
/**
@ -268,8 +278,8 @@ public class RedisTemplateHandler<K,V> {
* @param increment
* @return
*/
public <HK,HV> Long hIncrBy(K key, HK field, long increment) {
return redisTemplate.<HK,HV>opsForHash().increment(key, field, increment);
public <HK, HV> Long hIncrBy(K key, HK field, long increment) {
return redisTemplate.<HK, HV>opsForHash().increment(key, field, increment);
}
/**
@ -280,8 +290,8 @@ public class RedisTemplateHandler<K,V> {
* @param delta
* @return
*/
public <HK,HV> Double hIncrByFloat(K key, HK field, double delta) {
return redisTemplate.<HK,HV>opsForHash().increment(key, field, delta);
public <HK, HV> Double hIncrByFloat(K key, HK field, double delta) {
return redisTemplate.<HK, HV>opsForHash().increment(key, field, delta);
}
/**
@ -310,8 +320,8 @@ public class RedisTemplateHandler<K,V> {
* @param key
* @return
*/
public <HK,HV> List<HV> hValues(K key) {
return redisTemplate.<HK,HV>opsForHash().values(key);
public <HK, HV> List<HV> hValues(K key) {
return redisTemplate.<HK, HV>opsForHash().values(key);
}
/**
@ -321,8 +331,8 @@ public class RedisTemplateHandler<K,V> {
* @param options
* @return
*/
public <HK,HV> Cursor<Map.Entry<HK, HV>> hScan(K key, ScanOptions options) {
return redisTemplate.<HK,HV>opsForHash().scan(key, options);
public <HK, HV> Cursor<Map.Entry<HK, HV>> hScan(K key, ScanOptions options) {
return redisTemplate.<HK, HV>opsForHash().scan(key, options);
}
/**------------------zSet相关操作--------------------------------*/
@ -368,6 +378,7 @@ public class RedisTemplateHandler<K,V> {
public Double zIncrementScore(K key, V value, double delta) {
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
/**
* 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
*
@ -411,7 +422,7 @@ public class RedisTemplateHandler<K,V> {
* @return
*/
public Set<ZSetOperations.TypedTuple<V>> zRangeWithScores(K key, long start,
long end) {
long end) {
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
}
@ -436,7 +447,7 @@ public class RedisTemplateHandler<K,V> {
* @return
*/
public Set<ZSetOperations.TypedTuple<V>> zRangeByScoreWithScores(K key,
double min, double max) {
double min, double max) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
}
@ -449,7 +460,7 @@ public class RedisTemplateHandler<K,V> {
* @return
*/
public Set<ZSetOperations.TypedTuple<V>> zRangeByScoreWithScores(K key,
double min, double max, long start, long end) {
double min, double max, long start, long end) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,
start, end);
}
@ -475,7 +486,7 @@ public class RedisTemplateHandler<K,V> {
* @return
*/
public Set<ZSetOperations.TypedTuple<V>> zReverseRangeWithScores(K key,
long start, long end) {
long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start,
end);
}
@ -489,7 +500,7 @@ public class RedisTemplateHandler<K,V> {
* @return
*/
public Set<V> zReverseRangeByScore(K key, double min,
double max) {
double max) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}
@ -516,7 +527,7 @@ public class RedisTemplateHandler<K,V> {
* @return
*/
public Set<V> zReverseRangeByScore(K key, double min,
double max, long start, long end) {
double max, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max,
start, end);
}
@ -1116,7 +1127,7 @@ public class RedisTemplateHandler<K,V> {
* @return
*/
public V lBRightPopAndLeftPush(K sourceKey, K destinationKey,
long timeout, TimeUnit unit) {
long timeout, TimeUnit unit) {
return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
destinationKey, timeout, unit);
}

View File

@ -1,3 +1,3 @@
## Auto Configure
org.springframework.boot.cn.zyjblogs.starter.feign.autoconfigure.EnableAutoConfiguration=\
cn.zyjblogs.starter.redis.config.RedisConfig
cn.zyjblogs.starter.redis.config.RedisConfiguration

View File

@ -18,6 +18,8 @@
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<!--编译编码-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- <dependency>-->

View File

@ -2,8 +2,10 @@ package cn.zyjblogs.starter.web.autoconfig;
import cn.zyjblogs.starter.web.apiversion.ApiVersionRequestMappingHandlerMapping;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
* @author zhuyijun
@ -15,4 +17,9 @@ public class ApiVersionWebMvcAutoConfiguration extends WebMvcConfigurationSuppor
public RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new ApiVersionRequestMappingHandlerMapping();
}
@Override
protected void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(new InternalResourceViewResolver());
}
}

View File

@ -39,39 +39,6 @@ public class Knife4jAutoConfigurationConfig {
@Value("${spring.application.name}")
private String applicationName;
// @Bean
// public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
// return new BeanPostProcessor() {
//
// @Override
// public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
// customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
// }
// return bean;
// }
//
// private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
// List<T> copy = mappings.stream()
// .filter(mapping -> mapping.getPatternParser() == null)
// .collect(Collectors.toList());
// mappings.clear();
// mappings.addAll(copy);
// }
//
// @SuppressWarnings("unchecked")
// private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
// try {
// Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
// field.setAccessible(true);
// return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
// } catch (IllegalArgumentException | IllegalAccessException e) {
// throw new IllegalStateException(e);
// }
// }
// };
// }
@Bean(value = "defaultApi")
public Docket defaultApi() {
return new Docket(DocumentationType.SWAGGER_2)
@ -138,4 +105,5 @@ public class Knife4jAutoConfigurationConfig {
authorizationScopes[0] = authorizationScope;
return Lists.newArrayList(new SecurityReference("BearerToken", authorizationScopes));
}
}