Spring Security Oauth2实践(3) - 单点登录(SSO)

本次分享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;
    }
}

验证

  1. 登录客户端A
    访问服务http://localhost:8081/clientA/loginUserInfo跳转到登录页面,登录后api返回json信息如下。

客户端1

  1. 访问客户端B
    访问客户端Bhttp://localhost:8082/clientB/loginUserInfo时,发现并没有跳转到Oauth2服务端登录页面,而是授权后直接跳转到api并返回json信息
    客户端2

如何登出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失效。

代码地址

https://gitee.com/jeang/oauth2-demo.git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章