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認證伺服器(認證伺服器搭建
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) {
}
});