近日學習Spring Security框架,學習到利用安全框架完成系統的安全通道控制時,來來回回遇到了不少問題。spring教程上寫的略簡單,對於我等小白來講不足以支撐看書編碼,好在網絡上有資料可以查詢,在吸取了他人經驗,再結合自身的調試,最終實現了想要的效果。接下來,我就一步一步還原這個實現的過程,請往下看。
一、關於Tomcat的證書安裝,ssl監聽端口的實現說明
使用Tomcat啟用ssl,需要在server.xml文件中 添加ssl請求的監聽設置。方式有多種,這里提供一種,不是重點,不做贅述。
1.使用jdk的keytool工具,生成服務端證書
keytool -genkeypair -alias tomcat -keyalg RSA -keypass 123456 -storepass 123456 -keystore E:/tomcat.keystore
2.配置server.xml的ssl監聽
<Connector protocol="org.apache.coyote.http11.Http11NioProtocol" port="8443" minSpareThreads="5" maxSpareThreads="75" enableLookups="true" disableUploadTimeout="true" acceptCount="100" maxThreads="200" scheme="https" secure="true" SSLEnabled="true" clientAuth="false" sslProtocol="TLS" keystoreFile="E:/tomcat.keystore" keystorePass="123456"/>
3.(可以選擇)直接配置web.xml,完成安全通道的攔截開啟。這種方式不需要spring security框架。
<login-config>
<auth-method>CLIENT-CERT</auth-method>
<realm-name>Client Cert Users-only Area</realm-name>
</login-config>
<security-constraint>
<web-resource-collection>
<web-resource-name >SSL</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
</web-app>
二、驗證Spring Security 安全通道設置的實現說明
1、參考spring教程說明,完成第一次的啟用https的嘗試。對 /free/** 的請求開啟安全連接。
- 我的設置代碼 ( .and().requiresChannel().antMatchers("/free/**").requiresSecure())
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * HTTP請求處理 */ @Override protected void configure(HttpSecurity http) throws Exception { http .formLogin().loginPage("/user/login.do") .defaultSuccessUrl("/free/list.do")//啟用FORM登錄
.and().authorizeRequests().antMatchers("/user/login.do").permitAll()//登錄頁允許所有人訪問
.and().authorizeRequests().antMatchers("/**/*.do").authenticated() .and().requiresChannel().antMatchers("/free/**").requiresSecure() //.channelProcessors(getChannelProcessors())
.and().csrf().disable(); //暫時禁用CSRF
}
- 測試一下。報出了一堆filter執行的錯誤,並且將請求的路徑也改了,多了一級applicationContext。
- 跟蹤源碼,查看一下錯誤原因。堆棧調用過程這里就不細講了,這里只說一下問題根源。
如圖1,調試發現需要啟動安全訪問的請求都會進入這個方法,組裝重定向地址。redirectPort 應該返回https請求監聽端口,但是很遺憾的是這個值是null。
如圖2,接下來我看了下 getMappedPort這個方法,發現Spring Security默認是內置個兩組對應的映射端口(80->443,8080->8443)。到這里上面出錯就好理解了,我測試用的tomcat,設置的http請求監聽端口是8898,根本就找不到對應的https端口。知道了問題,接下來就開始整改吧。
2、個人源碼分析,暴力指定自己的通道請求處理,設置channelProcessors。
- 我的整改代碼
/** * HTTP請求處理 */ @Override protected void configure(HttpSecurity http) throws Exception { http .formLogin().loginPage("/user/login.do") .defaultSuccessUrl("/free/list.do")//啟用FORM登錄
.and().authorizeRequests().antMatchers("/user/login.do").permitAll()//登錄頁允許所有人訪問
.and().authorizeRequests().antMatchers("/**/*.do").authenticated() .and().requiresChannel().channelProcessors(getChannelProcessors()) .antMatchers("/free/**").requiresSecure() .and().csrf().disable(); //暫時禁用CSRF
} /** * 設置自己的通道處理器 * @return
*/
private List<ChannelProcessor> getChannelProcessors(){ List<ChannelProcessor> list = new ArrayList<ChannelProcessor>(); SecureChannelProcessor processor = new SecureChannelProcessor(); RetryWithHttpsEntryPoint entryPoint = ((RetryWithHttpsEntryPoint)processor.getEntryPoint()); //重新定義port映射
PortMapperImpl portMapper = new PortMapperImpl(); HashMap<String,String> maper = new HashMap<String,String>(); maper.put("80","443"); maper.put("8080","8443"); maper.put("8898","8443"); portMapper.setPortMappings(maper); entryPoint.setPortMapper(portMapper); list.add(processor); list.add(new InsecureChannelProcessor()); return list; }
- 測試一下,看看結果。不出所料,已經可以了!
3.、回頭想想,框架不會這么爛吧?不可能一個端口映射,還得自己分析一堆源碼才知道怎么玩?會不會是我自己沒找到門路?答案是肯定的,實際上框架真的已經提供了配置端口映射的方法。接下來就是優雅的第3版實現,請往下看。
- 我的整改代碼
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * HTTP請求處理 */ @Override protected void configure(HttpSecurity http) throws Exception { http .formLogin().loginPage("/user/login.do") .defaultSuccessUrl("/free/list.do")//啟用FORM登錄
.and().authorizeRequests().antMatchers("/user/login.do").permitAll()//登錄頁允許所有人訪問
.and().portMapper().http(8898).mapsTo(8443) //添加端口映射,做測試用
.and().authorizeRequests().antMatchers("/**/*.do").authenticated() .and().requiresChannel().antMatchers("/free/**").requiresSecure() .and().requiresChannel().anyRequest().requiresInsecure() .and().httpBasic() .and().csrf().disable(); //暫時禁用CSRF
}
- 測試一下,看看結果。非常不錯,這才是正確的道路!
4、試了幾把跳轉,發現點擊退出系統按鈕,退回到登錄頁面也成了https請求,不符合我想要的設置效果啊。按這個測試結果來看,猜測整個過程應該是這樣的。當我們成功進入一次https請求后,之后的請求因為都是指定的相對路徑,所以全部指向了8443端口。需要有個顯示的設置,讓其他請求被http架構處理。接下來是我的第4版實現,請往下看。
- 我的整改代碼
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * HTTP請求處理 */ @Override protected void configure(HttpSecurity http) throws Exception { String doUrl = "/**/*.do"; http .formLogin().loginPage("/user/login.do") .defaultSuccessUrl("/free/list.do")//啟用FORM登錄 .and().authorizeRequests().antMatchers("/user/login.do").permitAll()//登錄頁允許所有人訪問 .and().portMapper().http(8898).mapsTo(8443) //添加端口映射,做測試用 .and().authorizeRequests().antMatchers(doUrl).authenticated() .and().requiresChannel().antMatchers("/free/**",doUrl).requiresSecure() .and().requiresChannel().antMatchers(doUrl).requiresInsecure() .and().httpBasic() .and().csrf().disable(); //啟用CSRF }
- 測試一下,看看結果。可以了,現在可以做到只對/free/路徑下的請求開啟https安全通道了!
至此開啟安全訪問通道的功能實現就完成了。希望對讀到結尾的你有所幫助!如果有好的意見,歡迎評論交流。