原有多点登录改造单点登录实现

184 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

原有多点登录改造单点登录实现

1 背景

1.1 多点登录

之前每个系统登录,都是通过自己系统的账号密码进行登录。各站点的登录状态是互不认可,需要逐一进行手工登录,也就是多点登录。

未命名文件-导出.jpg

1.2 单点登录

多个站点client共用一台认证授权服务器server,用户数据库和认证授权模块共用。用户经由其中任何一个站点client登录后,可以免登录访问其他所有站点。而且,各站点间可以通过该登录状态直接交互。

未命名文件-导出 (1).jpg

2 OAuth2.0

OAuth2.0定义了四个授权类型:客户端凭证授权、密码凭证授权、隐式授权与授权码授权

参考:www.ruanyifeng.com/blog/2019/0…

OAuth2.0 授权 和 鉴权

授权服务:接入端以及登录用户的合法性进行验证,并颁发token

资源服务:保护服务,对非法请求进行拦截,对请求中的token进行鉴权服务

3 单点登录实现

本次多管理端单点登录的实现主要是通过对于AuthorizationServerConfiguration类、ResourceServerConfiguration类进行修改。 AuthorizationServerConfiguration类调整为客户端信息通过数据库中拿取,进行配置;ResourceServerConfiguration类添加特定访问资源服务。

image.png

3.1 oauth_client_details 表- OAuth 2.0 客户端

image.png

3.2 auth-server 实现

AuthorizationServerConfiguration类


@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisConnectionFactory connectionFactory;
    @Autowired
    private SysUserService userService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private WebResponseExceptionTranslator customWebResponseExceptionTranslator;
    @Autowired
    private IntegrationAuthenticationFilter integrationAuthenticationFilter;

    @Autowired
    private DataSource dataSource;

    @Bean
    public TokenEnhancer tokenEnhancer(){
        return new TokenEnhancer() {
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                if (accessToken instanceof DefaultOAuth2AccessToken){
                    DefaultOAuth2AccessToken token= (DefaultOAuth2AccessToken) accessToken;
                    Map<String, Object> additionalInformation = new LinkedHashMap<String, Object>();
                    additionalInformation.put("principal",authentication.getPrincipal());
                    token.setAdditionalInformation(additionalInformation);
                }
                return accessToken;
            }
        };
    }


    @Bean
    public RedisTokenStore tokenStore() {
        return new RedisTokenStore(connectionFactory);
    }

    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints
                .exceptionTranslator(customWebResponseExceptionTranslator)
                .authenticationManager(authenticationManager)
                .userDetailsService(userService)//若无,refresh_token会有UserDetailsService is required错误
                .tokenEnhancer(tokenEnhancer())
                .tokenStore(tokenStore());
    }

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

    /**
     *  配置客户端,可存在内存和数据库中
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .withClientDetails(clientDetails(dataSource));
    }

    /**
     * 获取客户端详细信息服务,JDBC实现
     * @return
     */
    @Bean
    public ClientDetailsService clientDetails(DataSource dataSource) {
        return new JdbcClientDetailsService(dataSource);
    }
}

ResourceServerConfiguration类

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    private static final String RESOURCE_ID = "resource-1";

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/token")
                .permitAll()
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable()
                .and()
                .httpBasic();
    }

    /**
     * 添加特定于资源服务器的属性
     *
     * @param resources
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore);

        resources
                .resourceId(RESOURCE_ID)
                .tokenServices(defaultTokenServices);
    }

获取用户信息

@RequestMapping("/user")
@RestController
@Log4j2
public class SysUserController extends BaseController {

    @Autowired
    private SysUserService sysUserService;

    @GetMapping("/getUser")
    public Principal getUser(Principal principal) {
        log.info("进入这个方法中");
        return  principal;
    }
}

3.3 auth-client 实现

application.yml

auth-server: http://localhost:8088/yinling-project

server:
  port: 8081
  servlet:
    context-path: /auth-client
    session:
      cookie:
        name: auth-client-1
        path: /

security:
  basic:
    enabled: false

  oauth2:
    client:
      clientId: 'auth-client-1'
      clientSecret: '123456'
      accessTokenUri: ${auth-server}/oauth/token
    resource:
      userInfoUri: ${auth-server}/user/getUser

启动类

@SpringBootApplication
@EnableOAuth2Sso
public class AuthClientApplication {

   public static void main(String[] args) {
      SpringApplication.run(AuthClientApplication.class, args);
   }

}

注意auth-client @EnableOAuth2Sso必须添加上。

ResourceServerConfiguration类

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    private static final String RESOURCE_ID = "resource-1";

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    /**
     * 添加特定于资源服务器的属性
     *
     * @param resources
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        resources
                .resourceId(RESOURCE_ID)
                .tokenServices(defaultTokenServices);
    }
    /**
     * 配置redis,使用redis存token
     * @return
     */
    @Bean
    public TokenStore tokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }
}

4 登录退出

目前采用的auth-server端直接进行退出登录操作,清除redis登录缓存信息(和之前一样)。

@FrameworkEndpoint
public class RevokeTokenEndpoint {
    @Autowired
    @Qualifier("consumerTokenServices")
    ConsumerTokenServices consumerTokenServices;

    @RequestMapping(method = RequestMethod.DELETE, value = "/oauth/token")
    public ResponseEntity<?> revokeToken(String access_token) {
        if (consumerTokenServices.revokeToken(access_token)) {
            return ResponseEntity.ok(new Result<>(ResultCode.OK.getCode(),"注销成功"));
        } else {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new Result<>(ResultCode.REVOKE_TOKEN_ERROR.getCode(),"注销失败"));
        }
    }
}