Spring Security Oauth2 自定义授权方式

Oauth2的默认授权模式有四种:

  • 授权码模式-authorization_code
  • 密码模式-password
  • 客户端模式-client_credentials
  • 隐式授权模式-implicit

token的运行流程:

  1. 在发起 URL+/oauth/token 获取token的请求后,实际上是请求 TokenEndpoint 类的postAccessToken或者getAccessToken方法,就相当于一个普通的concoller请求方法,根据请求类型是get或者post,其实get请求内部也是调用post请求的方法)
    在这里插入图片描述

  2. 在postAccessToken这个方法中调用TokenGranter类的grant方法来获取token,这个方法可以对请求的参数进行校验是否合法,是否给予令牌。
    在这里插入图片描述

  3. TokenGranter是一个接口,它有多个实现类,CompositeTokenGranter是其中之一,在grant方法中,会循环遍历所有的授权方式,根据请求参数携带的授权方式码匹配对应的授权处理实现类,调用实现类中的grant方法。
    在这里插入图片描述
    4.创建自定义授权处理类,我们可以继承TokenGranter来实现自定义的身份验证以便获取token,而AbstractTokenGranter是一个继承TokenGranter的实现类,一般我们都会继承这个类进行使用。
    在这里插入图片描述
    5.下面是自己的实现类,重写了getOAuth2Authentication方法,对参数进行了自定义的校验,校验的方法在下图中标识出。
    在这里插入图片描述
    6.对于参数的校验,在源码中,AuthenticationProvider是一个接口,里面有两个方法,一个是校验参数的方法,另一个则是根据当前认证信息匹配出对应的认证提供商类,这个接口有很多实现类,其中ProviderManager类是非常关键的,在这个类中的参数校验方法中,会根据当前要认证的对象,获取符合要求的所有的认证提供商,然后循环匹配出对应的认证提供商,在调取校验方法进行参数校验。
    在这里插入图片描述
    我们只需要实现ProviderManager接口的两个方法,自定义自己的参数校验方法,并且把这个自定义的ProviderManager加入到认证提供商集合中,在循环匹配的时候即可匹配到我们自定义的ProviderManager,进行参数校验。
    在这里插入图片描述
    7.把自定义的ProviderManager放入ProviderManager集合中,我的方法如下,在配置文件中,重写configure方法,配置ProviderManager,这里除了配置我们自定义的ProviderManager之外,还需要额外配置默认的密码授权模式的ProviderManager,否则client认证将不会通过。
    在这里插入图片描述
    8.实现了自定义token的获取,最后要做的是把这个自定义授权模式类,放入系统默认的授权模式集合中,这样在CompositeTokenGranter的grant方法中,在AuthorizationServerEndpointsConfigurer这个类中调用了getDefaultTokenGranters()方法,并且创建了 CompositeTokenGranter的实例对象,进行初始化,但系统已经把默认的授权模式全都写死在程序里了,因此我的解决思路是如下的
    在这里插入图片描述
    在这里插入图片描述
    9.把AuthorizationServerEndpointsConfigurer中,初始化默认授权方式的代码复制一下,在配置文件中额外重新配置自定义的模式,代码如下

package com.doudou.config;
 
import com.doudou.service.WeChatAbstractTokenGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
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.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Configuration
public class TokenGranterConfig {
    @Autowired
    private ClientDetailsService clientDetailsService;
 
    @Autowired
    private UserDetailsService userDetailsService;
 
    @Autowired
    private AuthenticationManager authenticationManager;
 
    @Autowired
    private TokenStore tokenStore;
 
    @Autowired
    TokenEnhancer tokenEnhancer;
 
    private AuthorizationCodeServices authorizationCodeServices;
 
    private boolean reuseRefreshToken = true;
 
    private AuthorizationServerTokenServices tokenServices;
 
    private TokenGranter tokenGranter;
 
    /**
     * 授权模式
     *
     * @return
     */
    @Bean
    public TokenGranter tokenGranter() {
        if (tokenGranter == null) {
            tokenGranter = new TokenGranter() {
                private CompositeTokenGranter delegate;
 
                @Override
                public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
                    if (delegate == null) {
                        delegate = new CompositeTokenGranter(getDefaultTokenGranters());
                    }
                    return delegate.grant(grantType, tokenRequest);
                }
            };
        }
        return tokenGranter;
    }
 
    /**
     * 程序支持的授权类型
     *
     * @return
     */
    private List<TokenGranter> getDefaultTokenGranters() {
        AuthorizationServerTokenServices tokenServices = tokenServices();
        AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
        OAuth2RequestFactory requestFactory = requestFactory();
 
        List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
        // 添加授权码模式
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory));
        // 添加刷新令牌的模式
        tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory));
        // 添加隐式授权模式
        tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory));
        // 添加客户端模式
        tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory));
        // 添加自定义授权模式(实际是密码模式的复制)
        tokenGranters.add(new WeChatAbstractTokenGranter(tokenServices, clientDetailsService, requestFactory));
        if (authenticationManager != null) {
            // 添加密码模式
            tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
        }
        return tokenGranters;
    }
 
    /**
     * TokenServices
     *
     * @return
     */
    private AuthorizationServerTokenServices tokenServices() {
        if (tokenServices != null) {
            return tokenServices;
        }
        this.tokenServices = createDefaultTokenServices();
        return tokenServices;
    }
 
    /**
     * 授权码API
     *
     * @return
     */
    private AuthorizationCodeServices authorizationCodeServices() {
        if (authorizationCodeServices == null) {
            authorizationCodeServices = new InMemoryAuthorizationCodeServices();
        }
        return authorizationCodeServices;
    }
 
    /**
     * OAuth2RequestFactory的默认实现,它初始化参数映射中的字段,
     * 验证授权类型(grant_type)和范围(scope),并使用客户端的默认值填充范围(scope)(如果缺少这些值)。
     *
     * @return
     */
    private OAuth2RequestFactory requestFactory() {
        return new DefaultOAuth2RequestFactory(clientDetailsService);
    }
 
    /**
     * 默认 TokenService
     *
     * @return
     */
    private DefaultTokenServices createDefaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore);
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setReuseRefreshToken(reuseRefreshToken);
        tokenServices.setClientDetailsService(clientDetailsService);
        tokenServices.setTokenEnhancer(tokenEnhancer);
        addUserDetailsService(tokenServices, this.userDetailsService);
        return tokenServices;
    }
 
    /**
     * 添加预身份验证
     *
     * @param tokenServices
     * @param userDetailsService
     */
    private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {
        if (userDetailsService != null) {
            PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
            provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(userDetailsService));
            tokenServices.setAuthenticationManager(new ProviderManager(Arrays.<AuthenticationProvider>asList(provider)));
        }
    }
}

10.授权认证服务端点配置

package com.doudou.config;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
 
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.security.KeyPair;
 
 
@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private DataSource dataSource;
 
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
 
    @Autowired
    private TokenGranter tokenGranter;
 
    @Autowired
    UserDetailsService userDetailsService;
 
    @Autowired
    AuthenticationManager authenticationManager;
 
    @Autowired
    TokenStore tokenStore;
 
    @Bean("keyProp")
    public KeyProperties keyProperties() {
        return new KeyProperties();
    }
 
    @Resource(name = "keyProp")
    private KeyProperties keyProperties;
 
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }
 
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(this.dataSource).clients(this.clientDetails());
    }
 
    @Bean
    @Autowired
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }
 
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyPair keyPair = new KeyStoreKeyFactory
                (keyProperties.getKeyStore().getLocation(), keyProperties.getKeyStore().getSecret().toCharArray())
                .getKeyPair(keyProperties.getKeyStore().getAlias(), keyProperties.getKeyStore().getPassword().toCharArray());
        converter.setKeyPair(keyPair);
        //配置自定义的CustomUserAuthenticationConverter
        DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
        accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
        return converter;
    }
 
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.accessTokenConverter(jwtAccessTokenConverter)
                .tokenGranter(tokenGranter) //四种授权模式+刷新令牌的模式+自定义授权模式
                .authenticationManager(authenticationManager)//认证管理器
                .tokenStore(tokenStore)//令牌存储
                .userDetailsService(userDetailsService)//用户信息service
                ;
    }
 
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.allowFormAuthenticationForClients()
                .passwordEncoder(new BCryptPasswordEncoder())
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }
 
 
}
 

11.当前clientid拥有的授权方式码是通过ClientDetails client = clientDetailsService.loadClientByClientId(clientId);获取的,在数据库中配置的,因此我们需要再oauth_client_details表中,在对应的clientid的authorized_grant_types字段中加上自定义的授权模式码

TydnCodeGranter.java

package com.sport.uaa.server.granter;

import com.sport.uaa.server.token.TydnCodeAuthenticationToken;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author xdd
 * @since 2024/2/19
 */
public class TydnCodeGranter extends AbstractTokenGranter {
    private static final String GRANT_TYPE = "tydn_code";

    private final AuthenticationManager authenticationManager;

    public TydnCodeGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices
            , ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
        String tydnCode = parameters.get("code");
        Authentication userAuth = new TydnCodeAuthenticationToken(tydnCode);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        userAuth = authenticationManager.authenticate(userAuth);
        if (userAuth == null || !userAuth.isAuthenticated()) {
            throw new InvalidGrantException("Could not authenticate code: " + tydnCode);
        }

        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }
}

TydnCodeAuthenticationToken.java

package com.sport.uaa.server.token;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.stereotype.Component;

import java.util.Collection;

/**
 * @author xdd
 * @since 2024/2/19
 */
public class TydnCodeAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    private final Object principal;

    public TydnCodeAuthenticationToken(String tydnCode) {
        super(null);
        this.principal = tydnCode;
        setAuthenticated(false);
    }

    public TydnCodeAuthenticationToken(Object principal,
                                       Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) {
        if (isAuthenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }
        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}

TydnCodeAuthenticationProvider.java

package com.sport.uaa.server.provider;

import com.sport.uaa.server.token.TydnCodeAuthenticationToken;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author xdd
 * @since 2024/2/19
 */
@Setter
public class TydnCodeAuthenticationProvider implements AuthenticationProvider {
    @Autowired(required = false)
    @Qualifier("tydnCodeUserDetailServiceImpl")
    private UserDetailsService userDetailsService;
    private PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) {
        TydnCodeAuthenticationToken authenticationToken = (TydnCodeAuthenticationToken) authentication;
        String tydnCode = (String) authenticationToken.getPrincipal();
        UserDetails user = userDetailsService.loadUserByUsername(tydnCode);
        if (user == null) {
            throw new InternalAuthenticationServiceException(tydnCode+"用户不存在");
        }
        TydnCodeAuthenticationToken authenticationResult = new TydnCodeAuthenticationToken(user, user.getAuthorities());
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return TydnCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

TydnCodeUserDetailServiceImpl.java

package com.sport.uaa.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.maxrocky.oidcsdk.client.OidcApiClient;
import com.maxrocky.oidcsdk.model.SportOidcApiResult;
import com.maxrocky.oidcsdk.model.response.OauthTokenResponse;
import com.maxrocky.oidcsdk.model.response.SsoUserLoginInfo;
import com.sport.common.auth.details.LoginAppUser;
import com.sport.log.annotation.LogAnnotation;
import com.sport.uaa.feign.UserFeignClient;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.util.Strings;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class TydnCodeUserDetailServiceImpl implements UserDetailsService {

    private final UserFeignClient userFeignClient;
    private final OidcApiClient oidcApiClient;

    @Override
    @LogAnnotation(module = "auth-server", recordRequestParam = false)
    public UserDetails loadUserByUsername(String tydnCode) throws UsernameNotFoundException {
        Long accountUserId = this.tydnUserInfo(tydnCode);
        //查询体育大脑用户信息
        LoginAppUser loginAppUser = userFeignClient.findByTydnAccountUserId(accountUserId);
        if (loginAppUser == null) {
            //用户不存在
            throw new AuthenticationCredentialsNotFoundException("用户不存在");
        }
        return loginAppUser;
    }

    /**
     * 根据tydnCode获取体育大脑用户信息
     */
    public Long tydnUserInfo(String code){
        SportOidcApiResult<OauthTokenResponse> sportOidcApiResult = oidcApiClient.token(code);
        if (!"200".equals(sportOidcApiResult.getCode())) {
            throw new RuntimeException("通过临时code获取授权码失败");
        }

        SsoUserLoginInfo ssoUser;
        OauthTokenResponse oauthTokenResponse = sportOidcApiResult.getData();
        try {
            Claims claims = parserJwt(oauthTokenResponse.getId_token());
            String userStr = claims.getSubject();
            if (Strings.isBlank(userStr)){
                throw new RuntimeException("JWT解析失败");
            }
            ssoUser = JSONObject.parseObject(userStr, SsoUserLoginInfo.class);
        } catch (Exception e) {
            throw new RuntimeException("JWT解析失败");
        }
        if(ssoUser.getUserType()!=2){
            throw new RuntimeException("只支持账号类用户登录!");
        }
        return ssoUser.getAccountUser().getId();
    }

    public Claims parserJwt(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(oidcApiClient.getAppSecret())
                    .parseClaimsJws(token)
                    .getBody();
        }catch (ExpiredJwtException e) {
            claims = e.getClaims();
        }
        return claims;
    }
}

UAAServerConfig.java


package com.sport.uaa.server;

import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;

import com.sport.uaa.server.granter.TydnCodeGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
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.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
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.code.RandomValueAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import com.sport.common.auth.props.PermitUrlProperties;
import com.sport.common.feign.FeignInterceptorConfig;
import com.sport.common.rest.RestTemplateConfig;
import com.sport.uaa.server.service.RedisAuthorizationCodeServices;
import com.sport.uaa.server.service.RedisClientDetailsService;
import com.sport.uaa.server.token.RedisTemplateTokenStore;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 
 * @version 创建时间:2017年11月12日 上午22:57:51
 */

@Configuration
@Import({RestTemplateConfig.class,FeignInterceptorConfig.class})
public class UAAServerConfig {

   

    /**
     * 声明 ClientDetails实现
     */
    @Bean
    public RedisClientDetailsService redisClientDetailsService(DataSource dataSource , RedisTemplate<String, Object> redisTemplate ) {
        RedisClientDetailsService clientDetailsService = new RedisClientDetailsService(dataSource);
        clientDetailsService.setRedisTemplate(redisTemplate);
        return clientDetailsService;
    }


    @Bean
    public RandomValueAuthorizationCodeServices authorizationCodeServices(RedisTemplate<String, Object> redisTemplate) {
        RedisAuthorizationCodeServices redisAuthorizationCodeServices = new RedisAuthorizationCodeServices();
        redisAuthorizationCodeServices.setRedisTemplate(redisTemplate);
        return redisAuthorizationCodeServices;
    }
    

    /**
     * 
     * @version 创建时间:2017年11月12日 上午22:57:51 默认token存储在内存中
     * DefaultTokenServices默认处理
     */
    @Component
    @Configuration
    @EnableAuthorizationServer
    @AutoConfigureAfter(AuthorizationServerEndpointsConfigurer.class)
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
        /**
         * 注入authenticationManager 来支持 password grant type
         */
        @Autowired
        private AuthenticationManager authenticationManager;

        @Autowired
        private UserDetailsService userDetailsService;
        @Autowired(required = false)
        private RedisTemplateTokenStore redisTokenStore;

        @Autowired(required = false)
        private JwtTokenStore jwtTokenStore;
        @Autowired(required = false)
        private JwtAccessTokenConverter jwtAccessTokenConverter;

        @Autowired
        private WebResponseExceptionTranslator webResponseExceptionTranslator;

        @Autowired
        private RedisClientDetailsService redisClientDetailsService;

        @Autowired(required = false)
        private RandomValueAuthorizationCodeServices authorizationCodeServices;

        /**
         * 配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory
         */
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

            if (jwtTokenStore != null) {
                endpoints.tokenStore(jwtTokenStore).authenticationManager(authenticationManager)
                        // 支持
                        .userDetailsService(userDetailsService);
                // password
                // grant
                // type;
            } else if (redisTokenStore != null) {
                endpoints.tokenStore(redisTokenStore).authenticationManager(authenticationManager)
                        // 支持
                        .userDetailsService(userDetailsService);
                // password
                // grant
                // type;
            }

            if (jwtAccessTokenConverter != null) {
                endpoints.accessTokenConverter(jwtAccessTokenConverter);
            }

            endpoints.authorizationCodeServices(authorizationCodeServices);

            endpoints.exceptionTranslator(webResponseExceptionTranslator);
            // 获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者
            List<TokenGranter> granterList = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));
            // 添加自定义授权模式授权者
            granterList.add(new TydnCodeGranter(authenticationManager, endpoints.getTokenServices(),
                    endpoints.getClientDetailsService(),
                    endpoints.getOAuth2RequestFactory()
            ));
            CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);

            //配置存储令牌策略
            endpoints
                    //添加自定义模式
                    .tokenGranter(tokenGranter(endpoints));
        }

        /**
         * 添加自定义授权类型
         *
         * @return List<TokenGranter>
         */
        private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
            // endpoints.getTokenGranter() 获取SpringSecurity OAuth2.0 现有的授权类型
            List<TokenGranter> granters = new ArrayList<TokenGranter>(Collections.singletonList(endpoints.getTokenGranter()));

            // 构建自定义授权类型
            TydnCodeGranter smsCodeTokenGranter = new TydnCodeGranter(authenticationManager,endpoints.getTokenServices(),
                    endpoints.getClientDetailsService(),endpoints.getOAuth2RequestFactory());
            // 向集合中添加定义授权类型
            granters.add(smsCodeTokenGranter);
            // 返回所有类型
            return new CompositeTokenGranter(granters);
        }

        /**
         * 配置应用名称 应用id
         * 配置OAuth2的客户端相关信息
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

            // if(clientDetailsService!=null){
            // clients.withClientDetails(clientDetailsService);
            // }else{
            // clients.inMemory().withClient("neusoft1").secret("neusoft1")
            // .authorizedGrantTypes("authorization_code", "password",
            // "refresh_token").scopes("all")
            // .resourceIds(SERVER_RESOURCE_ID).accessTokenValiditySeconds(1200)
            // .refreshTokenValiditySeconds(50000)
            // .and().withClient("neusoft2").secret("neusoft2")
            // .authorizedGrantTypes("authorization_code", "password",
            // "refresh_token").scopes("all")
            // .resourceIds(SERVER_RESOURCE_ID).accessTokenValiditySeconds(1200)
            // .refreshTokenValiditySeconds(50000)
            // ;
            // }
            clients.withClientDetails(redisClientDetailsService);
            redisClientDetailsService.loadAllClientToCache();
        }

        /**
         * 对应于配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            // url:/oauth/token_key,exposes
            security.tokenKeyAccess("permitAll()")
                    /// public key for token
                    /// verification if using
                    /// JWT tokens
                    // url:/oauth/check_token
                    .checkTokenAccess("isAuthenticated()")
                    // allow check token
                    .allowFormAuthenticationForClients();

            // security.allowFormAuthenticationForClients();
             security.tokenKeyAccess("permitAll()");
            // security.tokenKeyAccess("isAuthenticated()");
        }

    }

    @Configuration
    @EnableResourceServer
    @EnableConfigurationProperties(PermitUrlProperties.class)
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

        @Autowired
        private PermitUrlProperties permitUrlProperties;

        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/health");
            web.ignoring().antMatchers("/oauth/user/token");
            web.ignoring().antMatchers("/oauth/client/token");
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.requestMatcher(
                    /**
                     * 判断来源请求是否包含oauth2授权信息
                     */
                    new RequestMatcher() {
                        private AntPathMatcher antPathMatcher = new AntPathMatcher();

                        @Override
                        public boolean matches(HttpServletRequest request) {
                            // 请求参数中包含access_token参数
                            if (request.getParameter(OAuth2AccessToken.ACCESS_TOKEN) != null) {
                                return true;
                            }

                            // 头部的Authorization值以Bearer开头
                            String auth = request.getHeader("Authorization");
                            if (auth != null) {
                                if (auth.startsWith(OAuth2AccessToken.BEARER_TYPE)) {
                                    return true;
                                }
                            }
                            if (antPathMatcher.match(request.getRequestURI(), "/oauth/userinfo")) {
                                return true;
                            }
                            if (antPathMatcher.match(request.getRequestURI(), "/oauth/remove/token")) {
                                return true;
                            }
                            if (antPathMatcher.match(request.getRequestURI(), "/oauth/get/token")) {
                                return true;
                            }
                            if (antPathMatcher.match(request.getRequestURI(), "/oauth/refresh/token")) {
                                return true;
                            }

                            if (antPathMatcher.match(request.getRequestURI(), "/oauth/token/list")) {
                                return true;
                            }

                            if (antPathMatcher.match("/clients/**", request.getRequestURI())) {
                                return true;
                            }

                            if (antPathMatcher.match("/services/**", request.getRequestURI())) {
                                return true;
                            }
                            if (antPathMatcher.match("/redis/**", request.getRequestURI())) {
                                return true;
                            }
                            return false;
                        }
                    }

            ).authorizeRequests().antMatchers(permitUrlProperties.getIgnored()).permitAll().anyRequest()
                    .authenticated();
        }

    }


}

SecurityConfig.java

package com.sport.uaa.server.config;

import com.sport.common.auth.props.PermitUrlProperties;
import com.sport.uaa.server.handle.OauthLogoutHandler;
import com.sport.uaa.server.provider.PasswordAuthenticationProvider;
import com.sport.uaa.server.provider.SmsCodeAuthenticationProvider;
import com.sport.uaa.server.provider.TydnCodeAuthenticationProvider;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;

import javax.annotation.Resource;

/**
 * spring security配置
 */
@Configuration
@EnableWebSecurity
@EnableConfigurationProperties(PermitUrlProperties.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 登出处理器
     */
    @Resource
    private OauthLogoutHandler oauthLogoutHandler;

    @Resource
    private PasswordEncoder passwordEncoder;

    /**
     * 登出处理器
     */
    @Resource
    private UserDetailsService userDetailsService;

    /**
     * 白名单配置
     */
    @Resource
    private PermitUrlProperties permitUrlProperties;

    /**
     * 验证码配置
     */
    @Resource
    private ValidateCodeConfig validateCodeConfig;

    @Resource
    private PasswordAuthenticationProvider passwordAuthenticationProvider;

    /**
     * 短信登录模式
     */
    @Resource
    private SmsCodeAuthenticationProvider smsCodeAuthenticationProvider;


    /**
     * web访问安全控制
     *
     * @param web WebSecurity
     */
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers( "/configuration/ui", "/swagger-resources", "/configuration/security", "/login.html");
        web.ignoring().antMatchers("/js/**");
        web.ignoring().antMatchers("/css/**");
        web.ignoring().antMatchers("/health");
        // 忽略登录界面
        web.ignoring().antMatchers("/login.html");
        web.ignoring().antMatchers("/index.html");
        web.ignoring().antMatchers("/oauth/user/token");
        web.ignoring().antMatchers("/oauth/client/token");
        web.ignoring().antMatchers("/validata/code/**");
        web.ignoring().antMatchers("/sms/**");
        web.ignoring().antMatchers("/authentication/**");
        web.ignoring().antMatchers(permitUrlProperties.getIgnored());
    }

    /**
     * 认证管理
     *
     * @return 认证管理对象
     * @throws Exception 认证异常信息
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * http请求配置
     *
     * @param http HttpSecurity
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //禁用CSRF(Cross-site request forgery):跨站请求伪造,
        http.csrf().disable();
        //所有http请求必须通过安全认证
        http.authorizeRequests().anyRequest().authenticated();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        // 注册短信认证方式
        http.authenticationProvider(smsCodeAuthenticationProvider);
        http.apply(validateCodeConfig);
        //登出配置
        http.logout().logoutSuccessUrl("/login.html").logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()).addLogoutHandler(oauthLogoutHandler).clearAuthentication(true);
        // 解决不允许显示在iframe的问题
        http.headers().frameOptions().disable();
        http.headers().cacheControl();
    }


    /**
     * 配置自定义的AuthenticationProvider
     *
     * @param auth the {@link AuthenticationManagerBuilder} to use
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
        auth.authenticationProvider(passwordAuthenticationProvider);
        auth.authenticationProvider(tydnCodeAuthenticationProvider());
    }

    @Bean
    public TydnCodeAuthenticationProvider tydnCodeAuthenticationProvider(){
        return new TydnCodeAuthenticationProvider();
    }

}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值