springboot+security+oauth2+redis,使用redis和jackson序列化保存token

token的jackson序列化真是太坑了。。。。。。

ResourceServerConfiguration 资源服务器配置

这个配置的优先级高于WebSecurityConfigurerAdapter,所以HttpSecurity直接在这里配置了,省略WebSecurityConfig


import java.util.Map;

import org.cqg.test.common.util.SpringBeanUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 处理权限不足
     */
    @Autowired
    private RequestAccessDeniedHandler requestAccessDeniedHandler;
    /**
     * 处理 未授权 用户访问 非公有资源
     */
    @Autowired
    private LoginAuthenticationEntryPoint loginAuthenticationEntryPoint;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.authenticationEntryPoint(loginAuthenticationEntryPoint)
            .accessDeniedHandler(requestAccessDeniedHandler);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests().antMatchers("/oauth/**").permitAll() // 放行/oauth/token
            .antMatchers("/swagger-ui/**", "/swagger-resources/**", "/**/api-docs").permitAll() // 放行swagger
            .antMatchers("/favicon.ico").permitAll()//
            .antMatchers(HttpMethod.OPTIONS).permitAll()//
            .anyRequest().authenticated()//
            .and().exceptionHandling()
            // 权限不足处理程序 已授权的用户请求权限之外的资源时会交给这个类处理,即自定义403
            .accessDeniedHandler(requestAccessDeniedHandler)
            // 当一个未授权的用户请求非公有资源时, 这个类的commence方法将会被调用
            .authenticationEntryPoint(loginAuthenticationEntryPoint)
            // 启用iframe嵌套
            // .and().headers().frameOptions().disable()
            // 禁用csrf
            .and().csrf().disable();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return myAuthenticationManager();
    }

    @Bean
    GrantedAuthorityDefaults grantedAuthorityDefaults() {
        return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
    }

    /**
     * 自定义权限管理器,根据不同的client_id调用不同的处理
     * 
     * @return
     */
    private MyAuthenticationManager myAuthenticationManager() {
        MyAuthenticationManager myAuthenticationManager = new MyAuthenticationManager();

        Map<String, Object> beans = SpringBeanUtil.getBeansWithAnnotation(ClientLoginProcessingAnnotation.class);
        beans.forEach((name, bean) -> {
            ClientLoginProcessingAnnotation anno = bean.getClass().getAnnotation(ClientLoginProcessingAnnotation.class);
            if (anno.isDefault()) {
                logger.info("默认登录处理器:{} {}", anno.clientId(), anno.info());
                myAuthenticationManager.setDefalutProvider((AuthenticationProvider)bean);
            } else {
                myAuthenticationManager.putProvider(anno.clientId(), (AuthenticationProvider)bean);
            }
        });
        return myAuthenticationManager;
    }

}

认证服务器配置

使用jackson序列化oauth2的token,主要在这里实现。自定义一个TokenStore(继承RedisTokenStore),然后替换默认的JdkSerializationStrategy。


import javax.sql.DataSource;

import org.cqg.test.login.oauth2.token.MyRedisTokenStoreService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.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.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private RedisConnectionFactory connectionFactory;

    @Bean
    public TokenStore redisTokenStore() {
        MyRedisTokenStoreService tokenStore =
            new MyRedisTokenStoreService(jdbcClientDetailsService(), connectionFactory);

        return tokenStore;
    }

    @Bean
    public JdbcClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager).tokenStore(redisTokenStore());
    }
}

自定义RedisTokenStore


import java.util.Date;

import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
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.token.store.redis.RedisTokenStore;

public class MyRedisTokenStoreService extends RedisTokenStore {
    private ClientDetailsService clientDetailsService;

    public MyRedisTokenStoreService(ClientDetailsService clientDetailsService,
        RedisConnectionFactory connectionFactory) {
        super(connectionFactory);
        super.setSerializationStrategy(new JacksonSerializationStrategy());
        this.clientDetailsService = clientDetailsService;
        // Auto-generated constructor stub
    }

    @Override
    public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
        OAuth2Authentication result = readAuthentication(token.getValue());
        if (result != null) {
            // 如果token没有失效 更新AccessToken过期时间
            DefaultOAuth2AccessToken oAuth2AccessToken = (DefaultOAuth2AccessToken)token;

            // 重新设置过期时间
            int validitySeconds = getAccessTokenValiditySeconds(result.getOAuth2Request());
            if (validitySeconds > 0) {
                oAuth2AccessToken.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
            }
            super.removeAccessToken(token);
            // 将重新设置过的过期时间重新存入redis, 此时会覆盖redis中原本的过期时间
            storeAccessToken(token, result);
        }
        return result;
    }

    protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {
        if (clientDetailsService != null) {
            ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
            Integer validity = client.getAccessTokenValiditySeconds();
            if (validity != null) {
                return validity;
            }
        }
        // default 12 hours.
        int accessTokenValiditySeconds = 60 * 60 * 12;
        return accessTokenValiditySeconds;
    }
}

自定义JacksonSerializationStrategy

到这里基本已经完成了,但是OAuth2AccessToken和OAuth2Authentication的实现类没有无参构造器,jackson无法序列化成功,还需要做一些处理

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.TimeZone;

import org.cqg.test.common.jackson.HibernateCollectionMixIn;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.store.redis.StandardStringSerializationStrategy;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;

public class JacksonSerializationStrategy extends StandardStringSerializationStrategy {

    public RedisSerializer<Object> jackson2JsonRedisSerializer() {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
            new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        // 反序列化时候遇到不匹配的属性并不抛出异常 忽略json字符串中不识别的属性
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // 序列化时候遇到空对象不抛出异常 忽略无法转换的对象 “No serializer found for class com.xxx.xxx”
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        // PrettyPrinter 格式化输出
        // objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);

        // 指定时区,默认 UTC,而不是 jvm 默认时区
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
        // 日期类型处理
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

        // 序列化时将Long 转成string输出,js的long有精度错误
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);

        // 序列化时忽略懒加载对象
        objectMapper.registerModule(new Hibernate5Module());

        // 序列化时将hibernate的持久化集合转换成jdk的集合
        objectMapper.addMixIn(Collection.class, HibernateCollectionMixIn.class);
        
        objectMapper.addMixIn(OAuth2AccessToken.class, OAuth2AccessTokenMixIn.class);
        objectMapper.addMixIn(OAuth2Authentication.class, OAuth2AuthenticationMixin.class);

        // 序列化时将null转换成“”
        objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
            @Override
            public void serialize(Object paramT, JsonGenerator paramJsonGenerator,
                SerializerProvider paramSerializerProvider) throws IOException {
                // 设置返回null转为 空字符串""
                paramJsonGenerator.writeString("");
            }
        });
        // 序列化时添加类型,否则反序列化时会报错,无法转换
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        return jackson2JsonRedisSerializer;
    }

    @Override
    @SuppressWarnings("unchecked")
    protected <T> T deserializeInternal(byte[] bytes, Class<T> clazz) {
        return (T)jackson2JsonRedisSerializer().deserialize(bytes);
    }

    @Override
    protected byte[] serializeInternal(Object object) {
        return jackson2JsonRedisSerializer().serialize(object);
    }

}

OAuth2AccessToken的序列化与反序列化

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 {}
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;

import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.util.Assert;

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;

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();
    }

}

import java.io.IOException;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

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 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;

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;
    }
}

OAuth2Authentication的反序列化

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = OAuth2AuthenticationDeserializer.class)
public abstract class OAuth2AuthenticationMixin {}

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class OAuth2AuthenticationDeserializer extends JsonDeserializer<OAuth2Authentication> {
    Logger logger = LoggerFactory.getLogger(OAuth2AuthenticationDeserializer.class);

    @Override
    public OAuth2Authentication deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException {
        // TODO Auto-generated method stub
        ObjectMapper mapper = (ObjectMapper)jp.getCodec();
        JsonNode jsonNode = mapper.readTree(jp);
        JsonNode oauth2RequestNode = jsonNode.get("oauth2Request");
        JsonNode userAuthenticationNode = jsonNode.get("userAuthentication");
        JsonNode detailsNode = jsonNode.get("details");

        // Missing type id when trying to resolve subtype of [simple type, class
        // org.springframework.security.oauth2.provider.TokenRequest]: missing type id property '@class' (for POJO
        // property 'refresh')
        ObjectNode object = (ObjectNode)oauth2RequestNode;
        object.remove("refresh");
        OAuth2Request oAuth2Request = mapper.readValue(object.traverse(mapper), OAuth2Request.class);

        Authentication authentication = parseAuthentication(mapper, userAuthenticationNode);
        OAuth2Authentication token = new OAuth2Authentication(oAuth2Request, authentication);

        Object details = mapper.readValue(detailsNode.traverse(mapper), Object.class);
        token.setDetails(details);
        return token;
    }

    private Authentication parseAuthentication(ObjectMapper mapper, JsonNode jsonNode)
        throws JsonParseException, JsonMappingException, IOException {
        JsonNode principalNode = jsonNode.get("principal");
        ObjectNode object = (ObjectNode)principalNode;
        object.remove("authorities");

        UserDetails principal = mapper.readValue(object.traverse(mapper), new TypeReference<UserDetails>() {});
        Object credentials =
            mapper.readValue(jsonNode.get("credentials").traverse(mapper), new TypeReference<Object>() {});
        Set<SimpleGrantedAuthority> grantedAuthorities =
            parseSimpleGrantedAuthorities(mapper, jsonNode.get("authorities"));

        Class<? extends Object> requestClass = principal.getClass();
        Field officeCodeListField = null;
        // 2.1 获取属性字段
        try {
            officeCodeListField = requestClass.getDeclaredField("authorities");
            // 2.3 设置属性值
            officeCodeListField.setAccessible(true);
            officeCodeListField.set(principal, grantedAuthorities);
        } catch (Exception e) {
            logger.error("{}", e);
        }

        return new UsernamePasswordAuthenticationToken(principal, credentials, grantedAuthorities);
    }

    private Set<SimpleGrantedAuthority> parseSimpleGrantedAuthorities(ObjectMapper mapper, JsonNode jsonNode)
        throws JsonParseException, JsonMappingException, IOException {
        List<JsonNode> authorities =
            mapper.readValue(jsonNode.traverse(mapper), new TypeReference<List<JsonNode>>() {});
        Set<SimpleGrantedAuthority> grantedAuthorities = new HashSet<>(0);
        if (authorities != null && !authorities.isEmpty()) {
            authorities.forEach(s -> {
                if (s != null && !s.isEmpty()) {
                    grantedAuthorities.add(new SimpleGrantedAuthority(s.get("authority").asText()));
                }
            });
        }
        return grantedAuthorities;
    }

}

自定义登陆接口


import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.validation.Valid;

import org.cqg.test.common.data.RetMsg;
import org.cqg.test.common.errorcode.MyException;
import org.cqg.test.common.jackson.JacksonUtil;
import org.cqg.test.login.errorcode.ErrorEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

/**
 * 
 * @Description:TODO(自定义Oauth2获取令牌接口)
 * @ClassName:LoginController
 * @Date:2021年1月20日 下午3:04:13
 * @Author:程启罡
 */
@Api(tags = "登录管理")
@RestController
public class LoginController {

    @Autowired
    private TokenEndpoint tokenEndpoint;

    @ApiOperation(value = "登录")
    @RequestMapping(value = "/oauth/token", method = RequestMethod.POST)
    public RetMsg<LoginRetDTO> postAccessToken(@RequestHeader(name = "Authorization") String authorization,
        @RequestBody @Valid LoginDTO data) throws HttpRequestMethodNotSupportedException {

        Map<String, String> parameters = JacksonUtil.bean2map(data);

        Authentication principal = SecurityContextHolder.getContext().getAuthentication();
        User user = (User)principal.getPrincipal();
        parameters.put("client_id", user.getUsername());

        OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();

        LoginRetDTO token = new LoginRetDTO();
        token.setAccess_token(oAuth2AccessToken.getValue());
        token.setExpires_in(oAuth2AccessToken.getExpiresIn());
        token.setScope(oAuth2AccessToken.getScope().stream().collect(Collectors.joining(",")));
        token.setToken_type(oAuth2AccessToken.getTokenType());

        return new RetMsg<LoginRetDTO>(ErrorEnum.ER000000, token);
    }

    @ApiOperation(value = "获取权限")
    @RequestMapping(value = "/permission", method = RequestMethod.GET)
    public RetMsg<Set<String>> postAccessToken() {

        UserDetails authUser =
            (UserDetails)Optional.ofNullable(SecurityContextHolder.getContext()).map(e -> e.getAuthentication())
                .map(e -> e.getPrincipal()).orElseThrow(() -> new MyException(ErrorEnum.ER02M0002));

        Set<String> set =
            authUser.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());

        return new RetMsg<Set<String>>(ErrorEnum.ER000000, set);

    }

}

文章到此就结束了。另外,没有强迫症的就不必要用jackson去序列化token了,麻烦死了。。。。。


版权声明:本文为qq_33797412原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。