美文网首页Spring Security
Spring Security OAuth

Spring Security OAuth

作者: 薛定谔的猫_1406 | 来源:发表于2019-05-08 09:36 被阅读30次

认证服务器

1.实现认证服务器


认证服务器
资源服务器

OAuth核心

image.png
spring security核心流程,绿色的表示类,蓝色的表示接口(真正的实现放在括号里)
/oauth/authorize:验证接口, AuthorizationEndpoint
/oauth/token:获取token
/oauth/confirm_access:用户授权
/oauth/error:认证失败
/oauth/check_token:资源服务器用来校验token
/oauth/token_key:jwt模式下获取公钥;位于:TokenKeyEndpoint ,通过 JwtAccessTokenConverter 访问key


  • ClientDetailService:读取Client信息;
  • tokenRequst:封装了其他的参数,比如如果是密码模式的话,其他信息比如用户名、密码都封装在这里。同时会把ClientDetails也封装在TokenReuquest里。


    image.png
  • TokenRequest会调用TokenGranter,这个接口里封装了四种授权模式不同的实现,会根据grantType挑一个具体的实现来进行令牌生成的逻辑;
  • 不管是哪个生成逻辑,都会生成OAuth2Request(是ClientDetails和TokenRequest的整合)和Authentication(通过UserDetailsService读取出来的用户信息)
    -OAuth2Request和Authentication最终会生成OAuth2Authentication对象,包含了是哪个第三方应用 ,授权模式,授权用户的信息,授权的参数等都会封装在这个对象里;
  • OAuth2Authentication传递给AuthenticationServerTokenServices最终生成OAuth2AccessToken;
  • 调用



    认证会调用UserDetailService
  • DefaultTOkenServices生成token的逻辑


    同一个用户,假如再次获取token,其token没过期,会返回之前的token

重构用户名密码登录

image.png
  • 不管登录请求是什么、只要登录成功了,最终都会到一个AuthenticationSuccessHandler


    可以用到的逻辑
    在这个handler里之前返回的是用户信息,现在要返回TokenServices生成的token信息
// 在这个handler里要生成token返回用户,需要构建OAuth2Request,Authentication对象已经存在这个方法里
/**
 * 
 */
package com.imooc.security.browser.authentication;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.security.core.properties.LoginResponseType;
import com.imooc.security.core.properties.SecurityProperties;

/**
 * @author zhailiang
 *
 */
@Component("imoocAuthenticationSuccessHandler")
public class ImoocAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperties securityProperties;

    /*
     * (non-Javadoc)
     * 
     * @see org.springframework.security.web.authentication.
     * AuthenticationSuccessHandler#onAuthenticationSuccess(javax.servlet.http.
     * HttpServletRequest, javax.servlet.http.HttpServletResponse,
     * org.springframework.security.core.Authentication)
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {

        logger.info("登录成功");

        if (LoginResponseType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
        } else {
            super.onAuthenticationSuccess(request, response, authentication);
        }

    }

}

OAuth2Request的构建

令牌配置

/**
 * 认证服务器配置
 */
@Configuration
@EnableAuthorizationServer
public class ImoocAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private TokenStore tokenStore;

    @Autowired(required = false)
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired(required = false)
    private TokenEnhancer jwtTokenEnhancer;

//端点配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
        // 如果jwt转换器和jwt增强器都存在
        if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
            // 将其加入到增强链上
            TokenEnhancerChain enhancerChain = new TokenEnhancerChain();

            List<TokenEnhancer> enhancers = new ArrayList<>();
            enhancers.add(jwtTokenEnhancer);
            enhancers.add(jwtAccessTokenConverter);

            enhancerChain.setTokenEnhancers(enhancers);

            endpoints.tokenEnhancer(enhancerChain)
                    .accessTokenConverter(jwtAccessTokenConverter);
        }
    }


    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        super.configure(security);
    }


    /**
     * 客户端配置,认证服务器会给哪些第三方应用颁发token
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//        // 存储在内存中,也可以设置在数据库中
//        clients.inMemory()
//                // 设置clientid
//                .withClient("earthchen")
//                // 设置clientsecret
//                .secret("earthchensecret")
//                // 设置令牌过期时间
//                .accessTokenValiditySeconds(7200)
//                // 允许的授权模式
//                .authorizedGrantTypes("refresh_token", "authorization_code", "password")
//                // 配置oauth能获取的权限
//                .scopes("all")
//                .and()
//                // 第二个app
//                .withClient("earthchen_web");
        InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
        if (ArrayUtils.isNotEmpty(securityProperties.getOauth2().getClients())) {
            for (OAuth2ClientProperties config : securityProperties.getOauth2().getClients()) {
                builder.withClient(config.getClientId())
                        .secret(config.getClientSecret())
                        .accessTokenValiditySeconds(config.getAccessTokenValidateSeconds())
                        .authorizedGrantTypes("refresh_token", "authorization_code", "password")
                        // 设置刷新令牌的过期时间
                        .refreshTokenValiditySeconds(2592000)
                        .scopes("all", "write", "read");
            }
        }
    }
}
  1. 设置令牌的存储,负责令牌的存取。将其放在redis里。配置完就在上面的端点配置里配置tokenStore
@Configuration
public class TokenStoreConfig {

    /**
     * redis连接工厂
     */
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * 使用redis存储token的配置,只有在imooc.security.oauth2.tokenStore配置为redis时生效
     * @return
     */
    @Bean
    @ConditionalOnProperty(prefix = "earthchen.security.oauth2",
            name = "storeType",
            havingValue = "redis")
    public TokenStore redisTokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }


    /**
     * jwt的配置
     *
     * 使用jwt时的配置,默认生效
     */
    @Configuration
    @ConditionalOnProperty(prefix = "earthchen.security.oauth2",
            name = "storeType",
            havingValue = "jwt",
            matchIfMissing = true)
    public static class JwtTokenConfig {

        @Autowired
        private SecurityProperties securityProperties;

        @Bean
        public TokenStore jwtTokenStore() {
            return new JwtTokenStore(jwtAccessTokenConverter());
        }

        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter() {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            converter.setSigningKey(securityProperties.getOauth2().getJwtSigningKey());
            return converter;
        }

        @Bean
        @ConditionalOnBean(TokenEnhancer.class)
        public TokenEnhancer jwtTokenEnhancer(){
            return new ImoocJwtTokenEnhancer();
        }
    }
}

jwt的配置

  1. 依旧在TokenStoreConfig里
/**
     * jwt的配置
     *
     * 使用jwt时的配置,默认生效
     */
    @Configuration
    @ConditionalOnProperty(prefix = "earthchen.security.oauth2",
            name = "storeType",
            havingValue = "jwt",
            matchIfMissing = true)
    public static class JwtTokenConfig {

        @Autowired
        private SecurityProperties securityProperties;
 //负责token的存储,并不管理token的生成
        @Bean
        public TokenStore jwtTokenStore() {
            return new JwtTokenStore(jwtAccessTokenConverter());
        }
  // 这个也是配置在端点里
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter() {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        / /设置jwt的密签   converter.setSigningKey(securityProperties.getOauth2().getJwtSigningKey());
            return converter;
        }

        @Bean
        @ConditionalOnBean(TokenEnhancer.class)
        public TokenEnhancer jwtTokenEnhancer(){
            return new ImoocJwtTokenEnhancer();
        }
    }

2.在jwt包里定义TokenEnhnhancer

/**
 * 自定义jwt token内的数据
 *  jwt token 增强器
 */
public class ImoocJwtTokenEnhancer implements TokenEnhancer {

    /**
     * 实现方法
     * @param accessToken
     * @param authentication
     * @return
     */
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String, Object> info = new HashMap<>();
// 往token写入的内容
        info.put("company", "earthchen");

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);

        return accessToken;
    }
}
  1. 同样将其配置在端点
 // 如果jwt转换器和jwt增强器都存在
        if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
  1. 可以通过这个接口访问用户信息,但是并不能拿到增强的信息。


    image.png
  2. 拿到增强信息
  • 添加依赖


    image.png
    image.png
  1. 令牌的刷新
    获取令牌同时发送refresh_token,刷新令牌,在用户无感知的情况下刷新access_token


    依旧配置clientId等,地址不变,但是grantType要变成refreshToken,此时即可刷新token

基于jwt实现sso(在其中一个网站登录,另外一个网站自动会登录,比如淘宝和天猫)

相关文章

网友评论

    本文标题:Spring Security OAuth

    本文链接:https://www.haomeiwen.com/subject/mixunqtx.html