一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
原有多点登录改造单点登录实现
1 背景
1.1 多点登录
之前每个系统登录,都是通过自己系统的账号密码进行登录。各站点的登录状态是互不认可,需要逐一进行手工登录,也就是多点登录。
1.2 单点登录
多个站点client共用一台认证授权服务器server,用户数据库和认证授权模块共用。用户经由其中任何一个站点client登录后,可以免登录访问其他所有站点。而且,各站点间可以通过该登录状态直接交互。
2 OAuth2.0
OAuth2.0定义了四个授权类型:客户端凭证授权、密码凭证授权、隐式授权与授权码授权
参考:www.ruanyifeng.com/blog/2019/0…
OAuth2.0 授权 和 鉴权
授权服务:接入端以及登录用户的合法性进行验证,并颁发token
资源服务:保护服务,对非法请求进行拦截,对请求中的token进行鉴权服务
3 单点登录实现
本次多管理端单点登录的实现主要是通过对于AuthorizationServerConfiguration类、ResourceServerConfiguration类进行修改。 AuthorizationServerConfiguration类调整为客户端信息通过数据库中拿取,进行配置;ResourceServerConfiguration类添加特定访问资源服务。
3.1 oauth_client_details 表- OAuth 2.0 客户端
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(),"注销失败"));
}
}
}