作者
時(shí)間
2017年9月
簡述
這段時(shí)間一直在不斷的嘗試配置和使用Spring Security, 取得了一些成果盒粮,也大概的理解了一些其中的原理瓷产,擔(dān)心日后忘記气筋,特此記錄一下,作為備忘浦妄。
Spring 的配置分為XML和Java兩種七咧,還有一種是混合贷笛。我用的是混合踏施。至于組件管理石蔗,我用的是Gradle。Maven其實(shí)也差不多畅形。
環(huán)境
- Intellij Idea
- gradle
- java 1.8
- tomcat 9
首先是一些依賴的組件
build.gradle
group 'com.kgl1688'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'war'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework:spring-webmvc:4.2.3.RELEASE'
compile 'org.springframework.security:spring-security-web:4.2.3.RELEASE'
compile 'org.springframework.security:spring-security-config:4.2.3.RELEASE'
providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.0'
testCompile group: 'junit', name: 'junit', version: '4.11'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
先配置Spring MVC
Spring MVC 的Java配置
src/com/kgl1688/config/WebApplicationInitializer.java
package com.kgl1688.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {RootConfigu.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[] {"/*"};
}
}
src/com/kgl1688/config/WebConfig.java
package com.kgl1688.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
@ComponentScan("com.kgl1688.mvc")
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver internalResourceViewResolver =
new InternalResourceViewResolver();
internalResourceViewResolver.setPrefix("/WEB-INF/views/");
internalResourceViewResolver.setSuffix(".jsp");
return internalResourceViewResolver;
}
}
src/com/kgl1688/config/RootConfig.java
package com.kgl1688.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ComponentScan
public class RootConfig {
}
有了這三個(gè)類养距,一個(gè)Spring MVC的環(huán)境就配置完成了。其中幾個(gè)要點(diǎn)如下
WebApplicationInitializer
這個(gè)是Spring MVC 環(huán)境的啟動點(diǎn)日熬,之所以他是啟動點(diǎn)棍厌,因?yàn)樗^承自AbstractAnnotationConfigDispatcherServletInitializer
。更細(xì)節(jié)的原因可以度娘竖席。@EnableWebMvc
這個(gè)注解是啟動Spring MVC 的關(guān)鍵@ComponentScan
這個(gè)注解會啟動組件掃描耘纱,指示Spring 掃描使用了該注解的類所在的包及其下的所有子包的組件(RootConfiguration 在 com.kgl1688.config 包,那么com.kgl1688.config 以及其下的包都會被掃描)@ImportResource
這個(gè)注解可以引入XML類型的Beans的定義怕敬,這也是XML和Java混合配置的連接點(diǎn)
Spring Security 的Java配置
添加一個(gè)文件在src/com/kgl1688/config/ 目錄下:
src/com/kgl1688/config/SecurityWebApplicationInitializer.java
package com.kgl1688.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
不需要實(shí)現(xiàn)和重載任何方法揣炕,有就可以了帘皿,并且在什么包下都沒有關(guān)系东跪。其同樣會被Servlet 3.0 的容器探測到。運(yùn)行測試鹰溜,在后臺的LOG里會顯示如下的信息:
這時(shí)候的Spring Security 環(huán)境的初始化已經(jīng)被引入虽填,但還缺少需要的關(guān)鍵組件。這時(shí)候有兩種選擇:Java 配置 或者 XML配置文件
JAVA類配置
添加 SecurityConfig.java 到 src/com.kgl1688.config目錄下
package com.kgl1688.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
關(guān)鍵點(diǎn):
- 必須在com.kgl1688.config 包下曹动,因?yàn)橹拔覀兣渲肧pring MVC 環(huán)境的時(shí)候@ComponentScan指示在這個(gè)包里自動掃描組件
- @EnableWebSecurity斋日, 和 Spring MVC 一樣,這個(gè)注解會啟用Spring Security一些關(guān)鍵的組件墓陈。
自此恶守,Spring Security的環(huán)境就配置好了第献。
XML 配置
首先在RootConfig.java文件的類定義中增加
@ImportResource("classpath:security.xml")
package com.kgl1688.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource("classpath:security.xml")
@ComponentScan
public class RootConfig {
}
然后添加security.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="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 />
<user-service>
<user name="user" password="password" authorities="ROLE_USER" />
</user-service>
</b:beans>
這樣就有了最小的Spring Security環(huán)境和一些默認(rèn)的配置。
在默認(rèn)的情況下兔港,訪問除了/login, /logout 這些地址之外都需要認(rèn)證庸毫,在沒有認(rèn)證通過之前都會跳到登錄頁面: /login。因?yàn)槲覀兌x了一個(gè)用戶user, 密碼為password衫樊,我們可以用這個(gè)賬戶登錄飒赃。我們可以通過訪問/logout來退出登錄。這些都是Spring Security 給我們提供的科侈,包括登錄頁面载佳。
修改默認(rèn)的規(guī)則
如果我們只是希望那些我們希望保護(hù)的請求地址要求驗(yàn)證,其他的可以自由訪問臀栈。那么我們需要修改默認(rèn)的規(guī)則來達(dá)到我們的要求
現(xiàn)在我們希望 /secure下的所有請求地址都要求認(rèn)證蔫慧,那么我們可以配置如下:
- JAVA配置
更改src/com.kgl1688.config/SecurityConfig
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http);
http
.authorizeRequests()
.antMatchers("/secure/**")
.authenticated()
.and()
.formLogin()
.and()
.httpBasic();
}
- 等效的XML 配置
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="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>
<intercept-url pattern="/secure/**" access="authenticated" />
<form-login />
<http-basic />
</http>
<user-service>
<user name="user" password="password" authorities="ROLE_USER" />
</user-service>
</b:beans>
現(xiàn)在訪問 /secure 之下的地址都需要認(rèn)證,認(rèn)證方法支持 FormLogin(表單登錄)和 HttpBasic (API常用的方式权薯,無界面藕漱,通過請求頭攜帶認(rèn)證信息)兩種方式。我們可以通過瀏覽器訪問 /Secure 下的地址崭闲,如果未登錄的話肋联,會跳轉(zhuǎn)到登錄頁面。我們也可以通過 postman這樣的API測試工具測試使用HttpBasic方式刁俭。
為不同的請求地址指定不同的訪問角色
假設(shè)你希望所有的人可以訪問 /secure, 但是只有管理員能訪問 /secure/admin
- JAVA 配置
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/admin/**")
.hasRole("ADMIN")
.and()
.authorizeRequests()
.antMatchers("/secure/**")
.authenticated()
.and()
.formLogin()
.and()
.httpBasic();
}
- XML配置
<http>
<intercept-url pattern="/secure/admin/**" access="hasRole('ADMIN')" />
<intercept-url pattern="/secure/**" access="authenticated" />
<form-login />
<http-basic />
<logout />
</http>
<user-service>
<user name="user" password="password" authorities="ROLE_USER" />
<user name="admin" password="password" authorities="ROLE_ADMIN" />
</user-service>
這里唯一需要注意的是 intercept-url
定義的順序橄仍。Spring Security的規(guī)則是如果命中 pattern 中的條件,將會應(yīng)用此條規(guī)則牍戚,后續(xù)的其他的 intercept-url
都會忽略侮繁。
我們增加了一個(gè)新的用戶 admin,并且讓其具有ADMIN權(quán)限如孝。
為不同的請求提供不同的認(rèn)證方式
如果我們希望不同的請求地址使用不同的認(rèn)證方式宪哩,比如 /api/** 這樣的地址一般是作為api調(diào)用,而不是給瀏覽器頁面顯示給用戶的第晰,我們希望采用HttpBasic認(rèn)證方式锁孟,而其他仍然保留 FormLogin 的方式。
intercept-url
似乎不支持在其中加入 http-basic
, intercept-url
和 http-basic
是同一級別的茁瘦,都在 http
之下品抽。
但是可以有多個(gè)http
, 所以我們可以在不同的http
下指定不同的認(rèn)證方式。
- JAVA 配置
package com.kgl1688.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@EnableWebSecurity
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("USER").build());
manager.createUser(User.withUsername("admin").password("password").roles("ADMIN").build());
return manager;
}
@Configuration
@Order(1)
public class ApiWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
}
@Configuration
public class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/admin/**")
.hasRole("ADMIN")
.and()
.authorizeRequests()
.antMatchers("/secure/**")
.authenticated()
.and()
.formLogin()
.and()
.httpBasic();
}
}
}
這里必須要注意的是antMatcher("/api/**")
的寫法甜熔,他寫在authorizeRequests()
之前圆恤,這和寫在authorizeRequests()
之后有不同的含義,寫在前面是設(shè)置http對象的腔稀,而寫在authorizeRequests之后是設(shè)置authorizeRequests調(diào)用后返回的ExpressionInterceptUrlRegistry對象的盆昙。如果觀察下面與之等效的XML配置羽历,它實(shí)際上相當(dāng)與 <http> 的 pattern 屬性。
在XML配置中淡喜,<http>有出現(xiàn)的前后次序窄陡,可是在JAVA代碼中,反射機(jī)制并不會因?yàn)轭愒诖a中寫的位置的前后而有所區(qū)別拆火,所以必須依賴于 @Order注解跳夭。沒有@order注解的相當(dāng)于優(yōu)先級在最后。
另外一個(gè)要補(bǔ)充的知識點(diǎn)是JAVA代碼中配置的寫法規(guī)則们镜。http對象 和 XML配置中的 <http> 類似币叹。而 authorizeRequests 相當(dāng)于 <http>
中的 <intercept-url>
, formLogin
等價(jià)于 <form-login>
,httpBasic
等價(jià)于 <http-basic>
。 而 and() 就相當(dāng)于閉合一個(gè)元素模狭,比如 .formLogin().and()
就等于<form-login></form-login>
颈抚。因此 http.antMatcher("/api/**")
就等價(jià)于 <http pattern="/api/**">
- XML配置
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="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 pattern="/api/**">
<intercept-url pattern="/api/**" access="authenticated" />
<http-basic />
</http>
<http>
<intercept-url pattern="/secure/admin/**" access="hasRole('ADMIN')" />
<intercept-url pattern="/secure/**" access="authenticated" />
<form-login />
<http-basic />
<logout />
</http>
<user-service>
<user name="user" password="password" authorities="ROLE_USER" />
<user name="admin" password="password" authorities="ROLE_ADMIN" />
</user-service>
</b:beans>