最近在学习单点登录相关,调查了一下目前流行的单点登录解决方案:cas 和 oauth2,文章主要介绍oauth2 单点登录。希望这篇文章能帮助大家学习相关内容。
我们将使用两个单独的应用程序:
- 授权服务器–这是中央身份验证机制
- 客户端应用程序:使用SSO的应用程序
简而言之,当用户尝试访问客户端应用程序中的安全页面时,将首先通过身份验证服务器将其重定向为进行身份验证。
而且,我们将使用OAuth2中的“ 授权代码”授予类型来驱动身份验证的委派。
1、身份认证服务器(oauth2-server)
1.1 Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
注意:spring-security-oauth2>=2.3.0,<2.3.4中检测到已知的高严重性安全漏洞,请合理引用依赖
1.2 授权服务器配置
/**
* 认证服务器配置
* @author ltzhang
* @time 2019-11-28
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 注入权限验证控制器 来支持 password grant type
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* 注入userDetailsService,开启refresh_token需要用到
*/
@Autowired
private MyUserDetailsService userDetailsService;
/**
* 数据源
*/
@Autowired
private DataSource dataSource;
@Autowired
private RedisConnectionFactory connectionFactory;
@Autowired
private MyOAuth2WebResponseExceptionTranslator webResponseExceptionTranslator;
@Bean
public TokenStore tokenStore() {
// token 相关信息保存在 redis 中
return new RedisTokenStore( connectionFactory );
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 客户端信息保存在数据库中:表 oauth_client_details
// 如果oauth_client_details表中将autoApprove设置为true,
// 这样我们就不会重定向和提升为手动批准任何范围
clients.jdbc(dataSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//开启密码授权类型
endpoints.authenticationManager(authenticationManager);
//配置token存储方式
endpoints.tokenStore(tokenStore());
//自定义登录或者鉴权失败时的返回信息
endpoints.exceptionTranslator(webResponseExceptionTranslator);
//要使用refresh_token的话,需要额外配置userDetailsService
endpoints.userDetailsService( userDetailsService );
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
}
1.3 资源服务器配置
/**
* 资源提供端的配置
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private RestAuthAccessDeniedHandler deniedHandler;
@Autowired
private MyAuthenticationEntryPoint authenticationEntryPoint;
/**
* 这里设置需要token验证的url
* 可以在WebSecurityConfigurerAdapter中排除掉,
* 对于相同的url,如果二者都配置了验证
* 则优先进入ResourceServerConfigurerAdapter,进行token验证。而不会进行
* WebSecurityConfigurerAdapter 的 basic auth或表单认证。
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/user/me")
.and()
.authorizeRequests()
.antMatchers("/user/me")
.authenticated();
//需要的时候创建session,支持从session中获取认证信息,ResourceServerConfiguration中
//session创建策略是stateless不使用,这里其覆盖配置可创建session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(deniedHandler);
//此处是关键,默认stateless=true,只支持access_token形式,
// OAuth2客户端连接需要使用session,所以需要设置成false以支持session授权
resources.stateless(false);
}
}
1.4 安全配置
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class BrowerSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityAuthenticationProvider provider;
/**
* 如若需从数据库动态判断权限则实现 AccessDecisionManager
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login", "/oauth/authorize**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error=true")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutUrl("/logout")
.and()
//配置没有权限的自定义处理类
.exceptionHandling()
.accessDeniedPage("/403"); // 处理异常,拒绝访问就重定向到 403 页面
// 设置跨域问题
http.cors().and().csrf().disable();
http.sessionManagement().invalidSessionUrl("/login");
//单用户登录,如果有一个登录了,同一个用户在其他地方不能登录
http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/assets/**");
}
/**
* 自定义验证逻辑
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth){
auth.authenticationProvider(provider);
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
2 客户端应用配置(oauth2-client)
2.1 maven相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
2.2安全配置
@Configuration
@EnableOAuth2Sso
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private RestAuthAccessDeniedHandler deniedHandler;
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessUrl("http://localhost:8081/auth/logout")
.and()
//配置没有权限的自定义处理类
.exceptionHandling()
.accessDeniedHandler(deniedHandler);
http.csrf().disable();
// security 不开启 登录验证
http.httpBasic().disable();
}
}
2.3 application.yml配置
server:
port: 8082
servlet:
context-path: /ui
session:
cookie:
name: UISESSION
security:
oauth2:
client:
clientId: dev
clientSecret: 11
accessTokenUri: http://localhost:8081/auth/oauth/token
userAuthorizationUri: http://localhost:8081/auth/oauth/authorize
resource:
userInfoUri: http://localhost:8081/auth/user/me
注意事项:
- 禁用了默认的基本身份验证
- accessTokenUri是获取访问令牌的URI
- userAuthorizationUri是将用户重定向到的授权URI
- userInfoUri 用户端点的URI,以获取当前用户详细信息
实现效果预览
代码地址:
https://github.com/ltztao/spring-security-oauth2-server
https://github.com/ltztao/spring-boot-oauth2-client
参考文章:
https://www.baeldung.com/sso-spring-security-oauth2
发布于 2019-12-10