1. 程式人生 > >Nginx+Springboot+Security+CAS 整合方案-XML 實現SSO客戶端

Nginx+Springboot+Security+CAS 整合方案-XML 實現SSO客戶端

javaconfig版本: https://www.cnblogs.com/question-sky/p/7068511.html

以下使用的是SpringBoot 2.1.1進行測試

0 Maven引用

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- 新增spring security cas支援 -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-cas</artifactId>
        </dependency>

1 Springboot 啟動類註解和配置掃描

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)  //  啟用方法級別的許可權認證
@ImportResource(locations={"classpath:spring/spring-security.xml"})
server.servlet.context-path=/b  //設定專案的全域性URL字首(此處只是專案需求,與整合無關)

2 配置spring-security.xml

  localhost:8080 為CAS認證伺服器(認證伺服器搭建 

https://blog.csdn.net/shanchahua123456/article/details/85547516

  localhost:8787 為當前專案

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
						http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">

    <!-- http標籤是 spring security 配置標籤-->
    <http pattern="/static/**" security="none"></http>
    <http pattern="/css/**" security="none"></http>
    <http pattern="/img/**" security="none"></http>
    <http pattern="/js/**" security="none"></http>
    <http pattern="/plugins/**" security="none"></http>

    <!-- security 配置  use-expressions:是否啟動SPEL表示式 預設是true -->
    <!-- cas入口點引用  entry-point-ref是security框架接入第三方服務的通用入口     -->
    <http use-expressions="true" entry-point-ref="casProcessingFilterEntryPoint">
        <!-- security 配置 頁面的攔截規則-->
        <intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
        <intercept-url pattern="/b/qq/**" access="hasRole('ROLE_USER') and hasIpAddress('10.10.10.3')" />
        <csrf disabled="true"/>
        <!-- html允許載入 同源iframe -->
        <headers>
            <frame-options policy="SAMEORIGIN"/>
        </headers>
        <!-- custom-filter為過濾器, position 表示將過濾器放在指定的位置上,before表示放在指定位置之前  ,after表示放在指定的位置之後  -->
        <!-- 將CAS的過濾器 加入security框架-->
        <custom-filter ref="casAuthenticationFilter"  position="CAS_FILTER" />
        <custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/>
        <custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/>
    </http>

    <!-- CAS入口點 開始 -->
    <!--  localhost:8080 為CAS認證伺服器地址-->
    <!--  localhost:8787 為本專案地址-->
     <beans:bean id="casProcessingFilterEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
         <!-- 單點登入CAS伺服器登入URL , localhost:8080 為CAS認證伺服器,需要登入時會重定向到此URL上-->
        <beans:property name="loginUrl" value="http://localhost:8080/cas/login"/>
        <beans:property name="serviceProperties" ref="serviceProperties"/>
    </beans:bean>
    <!--設定客戶端service的屬性-->
    <beans:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
        <!--設定回撥的service路徑 配置 自身工程的根地址+/login/cas 是固定寫法。必須是此格式,不然會造成死迴圈,出現“重定向過多”錯誤-->
        <!--注意:因為本專案設定了公共URL字首server.servlet.context-path=/b,所以根地址為localhost:8787/b,否則根地址是localhost:8787  -->
        <beans:property name="service" value="http://localhost:8787/b/login/cas"/>
</beans:bean>
<!-- CAS入口點 結束 -->


    <!-- 認證過濾器 開始 -->
    <beans:bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">
        <beans:property name="authenticationManager" ref="authenticationManager"/>
    </beans:bean>
    <!-- 認證管理器 -->
    <authentication-manager alias="authenticationManager">
        <authentication-provider  ref="casAuthenticationProvider">
        </authentication-provider>
    </authentication-manager>
    <!-- 認證提供者 -->
    <beans:bean id="casAuthenticationProvider"     class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
        <beans:property name="authenticationUserDetailsService">
            <beans:bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
                <beans:constructor-arg ref="userDetailsService" />
            </beans:bean>
        </beans:property>
        <!--serviceProperties屬性主要應用於ticketValidator用於去cas服務端檢驗ticket-->
        <beans:property name="serviceProperties" ref="serviceProperties"/>
        <!-- ticketValidator 為票據驗證器 -->
        <beans:property name="ticketValidator">
            <beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
                <!--localhost:8080 為CAS伺服器 -->
                <beans:constructor-arg index="0" value="http://localhost:8080/cas"/>
            </beans:bean>
        </beans:property>
        <beans:property name="key" value="an_id_for_this_auth_provider_only"/>
    </beans:bean>
    <!-- 自定義授權類  其實現SpringSecurity的UserDetailsService介面,但是隻負責授權。密碼認證交給CAS伺服器完成-->
    <!-- 其在CAS認證之後 再執行授權  -->
    <beans:bean id="userDetailsService" class="com.example.nginxtest.UserDetailServiceImpl"/>

    <!-- 認證過濾器 結束 -->


    <!-- 單點登出  開始  -->
    <beans:bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter">
          <!--CAS 3.5 以上版本 需要配以下屬性 -->
          <!--設定cas服務端路徑字首,應用於front channel的登出請求-->
          <!--casServerUrlPrefix  與 cas伺服器的配置檔案相同\WEB-INF\cas.properties-->
          <beans:property name="casServerUrlPrefix" value="https://localhost:8080/cas"></beans:property>
          <beans:property name="ignoreInitConfiguration" value="true"></beans:property>
    </beans:bean>

    <!-- 經過以下配置,當用戶在位址列輸入 本工程+/logout/cas即可登出   -->
    <beans:bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
        <!--CAS伺服器登出URL   service= 為登出後跳轉URL-->
        <beans:constructor-arg value="http://localhost:8080/cas/logout?service=http://www.baidu.com"/>
        <beans:constructor-arg>
            <beans:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
        </beans:constructor-arg>
        <!--配置本地工程登出地址  /logout/cas為自定義登出路徑,cas框架自動與requestSingleLogoutFilter中的地址繫結-->
        <beans:property name="filterProcessesUrl" value="/logout/cas"/>
    </beans:bean>
    <!-- 單點登出  結束 -->

</beans:beans>

4 Security角色許可權認證。此處只為測試簡單,真實環境需要注入DAO層查詢許可權。

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.ArrayList;
import java.util.List;

public class UserDetailServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("經過認證類:"+username);

        List<GrantedAuthority> authorities=new ArrayList();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));

        return new User(username,"",authorities);
    }

}

5 NGINX配置

經測試使用瀏覽器通過NGINX訪問兩個配置了CAS登入的不同服務,可以實現SSO。


        location /b {
           proxy_pass  http://192.168.1.55:8787; 
 
           proxy_set_header X-Real-IP $remote_addr; 
           proxy_set_header Host $host;   
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_set_header Cookie $http_cookie;
        }

        location /a {
           proxy_pass  http://192.168.1.55:8788; 
 
           proxy_set_header X-Real-IP $remote_addr; 
           proxy_set_header Host $host;   
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_set_header Cookie $http_cookie;
        }

7 Security框架取得使用者資訊

  方案一:SecurityContextHolder中獲取

  可以自定義實現UserDetails類

//獲得當前登陸使用者對應的物件
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext()
    .getAuthentication()
    .getPrincipal();

//獲得當前登陸使用者所擁有的所有許可權
GrantedAuthority[] authorities = userDetails.getAuthorities();

方案二:session中獲取

SecurityContextImpl securityContextImpl = (SecurityContextImpl) request
.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
// 登入名
securityContextImpl.getAuthentication().getName();
// 登入密碼,未加密的
securityContextImpl.getAuthentication().getCredentials();

WebAuthenticationDetails details = (WebAuthenticationDetails) securityContextImpl
.getAuthentication().getDetails();
// 獲得訪問地址
details.getRemoteAddress();
// 獲得sessionid
details.getSessionId();
// 獲得當前使用者所擁有的許可權
List<GrantedAuthority> authorities = (List<GrantedAuthority>) securityContextImpl
.getAuthentication().getAuthorities();
for (GrantedAuthority grantedAuthority : authorities) {
System.out.println("Authority" + grantedAuthority.getAuthority());
}

8 CSRF+AJAX跨站偽造

上邊配置是關閉CSRF。若開啟的話,結合AJAX需要注意。

http://www.cnblogs.com/Joeee/p/7544573.html

https://www.cnblogs.com/zhufu9426/p/7814084.html

https://blog.csdn.net/starrrr2/article/details/50074445

html設定:全域性index頁面設定即可, 不需要每個頁面都寫

<meta name="_csrf" content="${_csrf.token}" />
<meta name="_csrf_header" content="${_csrf.headerName}" />

ajax 請求:

var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");

 $.ajax({
          url: "./deleteRes",
          method: 'post',
          dataType: 'json',
          data: {
            resIds: str,
          },
          beforeSend: function(request) {
            request.setRequestHeader(header, token);
          }
,
          success: function(resp) {
           }
     });