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

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