本次分享Oauth2的第三个实践——SSO单点登录。这个场景经常用于企业开发,集成多方系统认证。
CAS与Oauth2?
业界实现SSO一般采用CAS方案,案例也很多,也有很多其它标准和实现。CAS与Oauth2在实现SSO上的区别,可参考CAS的单点登录和oauth2的最大区别、 SSO with CAS or OAuth?两篇文章,相信读者读完后会有收获。
CAS是侧重于认证,Oauth2是认证、授权或控制对某些资源的访问权限。CAS客户端登录后获取到用户的资源后,判断是否有权限访问自己,而Oauth2客户端是需要获取用户资源,且需要Oauth2认证的客户端才可以访问。
在实现上CAS一般是不会关心具体是哪个客户端(可以自定义实现),而Oauth2服务端是给每个客户端都分配一个appId和secret才能访问Oauth2Resource并获取用户信息。
个人觉得,在企业SSO开发中,如果仅仅只需要认证,各系统间不需要授权访问,则可以采用CAS;如果需要认证、SSO,并且各系统间有资源访问认证需求、还有APP端对接,可以采用Oauth2实现SSO+授权。
客户端实现
相同的对接方式实现两个不同客户端,展示SSO的实践。
Maven依赖
springboot、spring-boot-starter-security版本均为2.1.11,spring-security-oauth2-autoconfigure版本为2.1.4.RELEASE,各版本对应匹配信息可参考maven中央仓库查询。
<dependencies>
<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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
项目配置
对接Oauth2需要在Oauth2服务端注册客户端,添加对应的客户端信息,并在客户端项目中配置。
客户端A
server:
port: 8081
servlet:
context-path: /clientA
session:
cookie:
name: clientA-SESSION
security:
oauth2:
client:
clientId: 980f9e83e04a4196a01300c9ff2a5899
clientSecret: fe5b9d71f29147dbbeb4dcea39b81e50
accessTokenUri: http://localhost:9011/auth/oauth/token
userAuthorizationUri: http://localhost:9011/auth/oauth/authorize
tokenName: access_token
resource:
userInfoUri: http://localhost:9011/auth/userInfo
server:
logoutUrl: http://localhost:9011/auth/logout
spring:
thymeleaf:
cache: false
logging:
level:
root: debug
客户端B
server:
port: 8082
servlet:
context-path: /clientB
session:
cookie:
name: clientB-SESSION
security:
oauth2:
client:
clientId: 83f49cf610f54d94b7e773049f353719
clientSecret: abd6d9d167c345ceb1ae1426a22dcc06
accessTokenUri: http://localhost:9011/auth/oauth/token
userAuthorizationUri: http://localhost:9011/auth/oauth/authorize
tokenName: access_token
resource:
userInfoUri: http://localhost:9011/auth/userInfo
server:
logoutUrl: http://localhost:9011/auth/logout
spring:
thymeleaf:
cache: false
logging:
level:
root: debug
@EnableOAuth2Sso接入配置
@EnableOAuth2Sso配置表示注入Oauth2Sso客户端自动配置
@Configuration
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
客户端服务
客户端提供以下服务用于测试,可用于前端交互获取登录用户信息。
@Slf4j
@Controller
public class IndexController {
@RequestMapping("loginUserInfo")
@ResponseBody
public Object loginUserInfo(Principal principal, HttpServletRequest request) {
if (null == principal) {
log.info("====> 用户未登录");
return null;
}
HttpSession session = request.getSession(false);
log.info("====> session id is {}", session.getId());
return principal;
}
}
验证
- 登录客户端A
访问服务http://localhost:8081/clientA/loginUserInfo
跳转到登录页面,登录后api返回json信息如下。
- 访问客户端B
访问客户端Bhttp://localhost:8082/clientB/loginUserInfo
时,发现并没有跳转到Oauth2服务端登录页面,而是授权后直接跳转到api并返回json信息
如何登出Logout?
logout登出在CAS中是需要强制实现,客户端不用具体开发,将session失效即可,具体交互则交给Cas client sdk
框架实现;而在Oauth2中并没有强制标准,客户端需要开发逻辑让Oauth2服务端登出实现,一般是采用302转发到服务端。
客户端配置
修改客户端配置如下
@Configuration
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Value("${security.oauth2.server.logoutUrl}")
private String serverLogoutUrl;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl(serverLogoutUrl)
.and().csrf().disable();
}
}
Oauth2服务端配置
Oauth2Server端需要实现登出接口,Client对接时跳转到此处即可
@Controller
public class LogoutController {
@RequestMapping("/logout")
public void exit(HttpServletRequest request, HttpServletResponse response) {
new SecurityContextLogoutHandler().logout(request, null, null);
try {
//sending back to client app
response.sendRedirect(request.getHeader("referer"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
小结
文章介绍CAS与Oauth2实现SSO单点登录的区别,给出Oauth2实现SSO的实践案例,结尾处给出登出的最佳实践,但是这个登出并不能保证单点登出,即A系统登出后,B系统无法访问,而CAS有这个实现机制,可能是其中任意系统登出后向其它客户系统发出logoutRequest,客户系统SDK拦截处理本地session失效。Oauth2也可以实现类似机制,需要客户端开发进行拦截本地session失效。