Spring Security是爲基於Spring的應用程序提供聲明式安全保護的安全性框架。Spring Security提供了完整的安全性解決方案。它可以在Web請求級別和方法調用級別處理身份認證和受權。html
Spring Security從兩個角度來解決安全性問題。它使用Servlet規範中的Filter保護Web請求並限制URL級別的訪問。Spring Security還可以使用Spring AOP保護方法調用——藉助於對象代理和使用通知,可以確保只有具有適當權限的用戶才能訪問安全保護的方法。java
Spring Security 3.2 被分紅了11個模塊web
ACL(access control list) 支持經過訪問控制列表爲域對象提供安全性正則表達式
切面(Aspects) 一個很小的模塊,當使用Spring Security註解時,會使用基於AspectJ的切面,而不是使用標準的Spring AOPspring
CAS客戶端(CAS Client) 提供與Jasig的中心認證服務進行集成功能數據庫
配置(configuration) 包含經過XML和Java配置Spring Security的功能支持數組
核心(Core) 提供Spring Security基本庫瀏覽器
加密(Cryptography) 提供了加密和密碼編碼的功能安全
LDAP 支持基於LDAP進行認證服務器
OpenID 支持使用OpenID進行集中式認證
Remoting 提供了對Spring Remoting的支持
標籤庫(tag library) Spring Security的JSP標籤庫
Web 提供了Spring Security基於Filter的Web安全性支持
應用程序的類路徑下至少要包含Core和Configuration這兩個模塊。
Spring Security經過一系列Servlet Filter來提供各類安全性功能。DelegatingFilterProxy是一個特殊的Servlet Filter。它將工做委託給一個javax.servlet.Filter實現類,這個實現類做爲一個<bean>註冊在Spring應用上下文中。
在web.xml中配置Servlet和Filter
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter>
經過java方式配置
public class SecurityWebInitializer extends AbstractSecurityWebApplicatuonInitializer{}
AbstractSecurityWebApplicationInitializer實現了WebApplicattionInitialzier,Spring會發現它,並在Web容器中註冊DelegatingFilterProxy。
無論咱們用web.xml仍是經過AbstractSecurityWebApplicationInitializer的子類來配置DelegatingFilterProxy,它都會攔截髮往應用中的請求,並將請求委託給ID爲springSecurityFilterChain的bean。
springSecurityFilterChain自己是另外一個特殊的Filter,他也被稱爲FilterChainProxy。它能夠連接任意一個或多個其餘的Filter。
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityAdapeter{ }
@EnableWebSecurity註解將會啓用Web安全功能,但實際上並無什麼用。Spring Security必須配置在一個實現了WebSecurityConfigurer的bean中。
@EnableWebMvcSecurity註解配置了一個Spring MVC參數解析器(argument resolver),這樣的話處理器方法可以經過帶有@AuthenicationPrincipal註解的參數得到認證用戶的principal。它同時還配置了一個bean,在使用Spring報表單綁定標籤庫來定義表單時,這個bean會自動添加一個隱藏的跨站請求僞造(cross-site request forger, CSRF)token輸入域。
經過重載WebSecurityConfigurerAdapter中的一個或多個方法(configure)來指定Web安全細節
configure(WebSecurity) 經過重載,配置Spring Security的Filter鏈
configure(HttpSecurity) 經過重載,配置如何經過攔截器保護請求
configure(AuthenticationManagerBuilder) 經過重載,配置user-detail服務
攔截請求:重寫protected void configure(HttpSecurity http) throws Exception{}
@Override protected void configure(HttpSecurity http) throws Exception{ http.authorizeRequests().anyRequest().authenticated() .and().formLogin().and().httpBasic(); }
上段代碼爲Spring Security的默認配置。經過調用AuthorizeRequests()和anyRequest().authenticated()就會要求全部進入應用的HTTP請求都要進行認證,同時配置Spring Security支持基於表單的登錄以及HTTP Basic方式的認證
@Override protected void configure(HttpSecurity http) throws Exception{ http.authorizeRequest() .antMathchers("/spitters/me").authenticated() // 對spitters/me請求進行認證 .antMatchers(HttpMethod.POST, "/spittles").authenticated()在 //對spittles的POST請求進行認證 .anyRequest().permitAll(); //對於其餘全部的請求都是容許的,不須要認證和任何的權限 }
configure()方法中獲得的HttpSecurity對象能夠在多個方面配置HTTP的安全性。調用authorizeRequests()方法返回的對象的方法來配置請求級別的安全性細節。
antMatchers()方法中設定的路徑支持Ant風格的通配符。如antMatchers("/spittles/**", "/spittles/mine").authenticated();
regexMatchers()方法則可以接受正則表達式來定義請求路徑。如regexMatchers("/spitters/.*").authenticated();
authenticated()要求在執行該請求時,必須已經登錄了應用,若是用戶沒有認證的話,Spring Security的Filter將會捕獲該請求,並將用戶重定向到應用的登錄頁面。permitAll()方法容許請求沒有任何的安全限制。requiresChannel()方法能爲各類URL密匙聲明所要求的通道。requiresSecure()表示將請求定向到HTTPS上
e.g: http.requiresChannel().antMatchers("/spitter/form").requiresSecure();
http.antMatchers("/").requiresInecure();
Spring Security3.2起,默認會開啓CSRF(cross-site request forgery)防禦。它經過一個同步token的方式來實現CSRF防禦的功能。它將會攔截狀態變化的請求並檢查CSRF token。若是請求中不包含CSRF token或token不能與服務器端的token相匹配,請求會失敗,並拋出CsrfException異常。
access(String) 若是給定的SpEL表達式計算結果爲true,就容許訪問
anonymous() 容許匿名用戶訪問
authenticated() 容許認證過的用戶訪問
denyAll() 無條件拒絕全部飯個萬寧
fullyAuthenticated() 若是用戶是完整證認證的話(不是經過remember-me功能認證的),就容許訪問
hasAnyAuthority(String...) 若是用戶具有給定權限中的某一個的話,就容許訪問
hasAnyRole(String...) 若是用戶具有給定角色中的某一個的話,就容許訪問
hasAuthhority(String) 若是用戶具有給定權限的話,就容許訪問
hasIpAddress(String) 若是請求來自給定IP地址的話,就容許訪問
hasRole(String) 若是用戶具有給定角色的話,就容許訪問
not() 對其餘訪問方法的結果求反
permitAll() 無條件容許訪問
rememberMe() 若是用戶是經過Remember-me功能認證的,就容許訪問
@Override protected void configure(HttpSecurity http) throws Exception{ http.authorizeRequests() .antMatchers("/spitter/ych").hasRole("SPITTER") .antMatchers(HttpMethod.POST, "/spittles").hasRole("SPITTER") .anyRequest().permitAll(); }
Spring Security擴展了SpEL以下:
authentication 用戶的認證對象
denyAll 結果始終爲false
hasAnyRole(list of roles) 若是用戶被授予了列表中任意的指定角色,結果爲true
hasRole(role) 若是用戶被受哦與了指定的角色,結果爲true
hasIpAddress(IP Address) 若是請求來自指定IP,結果爲true
isAnonymous() 若是當前用戶爲匿名用戶,結果爲true
isAuthenticated() 若是當前用戶進行認證的話,結果爲true
idFullyAuthenticated() 若是當前用戶進行了完整認證的話,結果爲true
isRememerMe() 若是當前用戶是用過Remember-me自動認證的,結果爲true
permitALL 始終爲true
principal 用戶的principal對象
e.g: antMathcers("/spitter/me").access("hasRole('ROLE_SPITTER') and hasIpAddress('192.168.1.1')")
用戶存儲:重寫protected void configure(AuthenticationManagerBuilder auth) throws Exception{}
使用基於內存的用戶存儲@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.inMemoryAuthentication().withUser("user").password("password").roles("USER") .and().withUser("admin").password("password").roles("USER", "ADMIN");
// auth.inMemoryAuthentication().withUser("user").password("password").authorities("ROLE_USER")
// .and().withUser("admin").password("password").authorities("ROLE_ADMIN"); }
經過調用auth.inMemoryAuthentication()啓動內存用戶存儲。withUser()方法爲內存用戶存儲添加新的用戶,它返回UserDetailsManagerConfigurer.UserDetailsBuilder。roles()方法是authorities()方法的簡寫形式。roles()方法所給定的值都會添加一個"ROLE_"前綴,並將其做爲權限授予給用戶。
配置用戶詳細信息的方法
accountExpired(boolean) 定義帳號是否已通過期
accountLocked(boolean) 定義帳戶是否已經鎖定
and() 用來鏈接配置
authorities(GrantedAuthority...) 授予某個用戶一項或多項權限
authorities(List<? extends GratedAuthority>) 授予某個用戶一項或多項權限
authorities(String...) 授予某個用戶一項或多項權限
credentialsExpired(boolean) 定義憑證是否已通過期
disabled(boolean) 定義帳號是否已被禁用
password(String) 定義用戶密碼
roles(String...) 授予某個用戶一項或多項角色
基於數據庫表進行認證
@Autowired DataSource dataSource; @Override protected void configure(AuthenticationManagerBuilder) throws Exception{ auth.jdbbcAuthentication().dataSource(dataSource); }
上段代碼是默認的最少配置,它對咱們的數據庫模式有一些要求,它預期存在某些存儲用戶數據的表。下面的代碼來自於Spring Security的內部:
public static final String DEF_USERS_BY_USERNAME_QUERY = "select username, password, enabled from users where userame = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "select username, authority from authorities where username = ?";
public static final String DEF_GROUP+AUTHORITIES_BY_USERNAME_QUERY = "select g.id, g.group_name. ga.authority from groups g, group_members gm, group_authorities ga where gm.username = ? and g.id = ga.group+id and g.id = gm.group_id ";
能夠經過usersByUsernameQuery()和authoritiesByUsernameQuery()來自定義查詢
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery("select username, password, true from Spitter where username = ?") .authorititiesByUsernameQuery("select username, 'ROLE_USER' from Spitter where username = ?")
.password(new StandardPasswordEncoder("53cr3t")); }
將默認的SQL查詢替換爲自定義的設計時,很重要的一點是要遵循查詢的基本協議。全部查詢都將用戶名做爲惟一的參數。認證查詢會選取用戶名、密碼以及啓用狀態信息。權限查詢會選取零行或多行包含該用戶名及其權限信息的數據。羣組權限查詢會選取零行或多行數據,每行數據都會包含羣組ID、羣組名稱以及權限。passwordEncoder()方法能夠接受Spring Security中PasswordEncoder接口的任意實現。Spring security的加密模塊包括了三個實現:BCryptPasswordEncoder、NoOpPasswordEncoder和StandardPasswordEncoder。
基於LDAP進行認證
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.jdbcAuthentication().userSearchFilter("{uid={0}}").groupSearchFilter("member={0}"); }
userSearchFilter()和groupSearchFilter()用來爲基礎LDAP查詢提供過濾條件,它們分別用於搜尋用戶和組。默認狀況下,對於用戶和組的基礎查詢都是空的,也就是代表搜索會在LDAP層級結構的跟開始。用戶能夠經過userSearchBase()和groupSearchBase()方法來更改查找用戶的基礎查詢和組指定的基礎查詢。
@Override protected void configure(AuthenticationMangagerBuilder auth) throws Exception{ auth.ldapAuthentication().userSearchBase("ou=people").userSearchFilter("(uid={0})") .groupSearchBase("ou=groups").groupSearchFilter("member={0}") .passwordCompare(); }
默認狀況下,在登陸表單中提供的密碼將會與用戶的LDAP條目中的userPassword屬性進行對比,若是密碼被保存在不一樣的屬性中們能夠經過passwordAttribute()方法來聲明密碼屬性的名稱。
contextSource()方法會返回一個ContextSourceBuilder對象,這個對象提供url()方法來指定LDAP服務器的地址。同時,ContextSourceBuilder還提供root()方法調用Spring Security內嵌的LDAP服務器。當LDAP服務器啓動時,它會嘗試在類路徑下尋找LDIF文件來加載數據。LDIF(LDAP Data Interchange Format)是以文本文件展示LDAP數據的標準方式。每條記錄能夠有一行或多行,每項包含一個名值對。記錄之間經過空行進行分割。若不想Spring從整個根路徑下搜索LDIF文件的話,能夠經過調用ldif()方法來明確指定加載哪一個LDIF文件。
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.ldapAuthentication().userSearchBase("ou=people").userSearchFilter("(uid={0})") .groupSearchBase("ou=groups").groupSearchFilter("member={0}") .contextSource().root("dc=habuma,dc=com").ldif("classpath:users.lidf"); }
配置自定義的用戶服務
咱們須要實現UserDetailsService接口並重載loadUserByUsername()方法,而後在congifure中調用。
@Autowired private SpitterRepository repository; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.userDetailsService(new SpitterUserService(repository)); }
public class SpitterUserService implements UserDetailsService{ private final SpitterRepository repository; public SpitterUserService(SpittreRepository repository){ this.repository = repository; } @Override public UserDetais loadUserByUsername(String username) throws UserNameNotFoundException{ Spitter spitter = repository.findByUsername(username); if(spitter != null){ List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); authorities.add(new SimpleGrantedAuthority("ROLE_SPITTER")); return new User(spitter.getUsername(), spitter.getPassword(), authorities); } throw new UsernameNotFoundException(username + " not found"); } }
HTTP Basic認證(Http Basic Authentication)會直接經過HTTP請求自己,對要訪問應用程序的用戶進行認證。能夠經過http.httpBasic()進行開啓認證,並調用realmName("Spittr")方法來指定域
Spring Security提供Remember-me功能。經過http.rememberMe()來設置。默認狀況下,spring會在cookie中存儲一個token,這個token最多兩週有效,但能夠經過設置tokenValiditySecounds(millionseconds)。存儲在cookie中的token包含用戶名,密碼,過時時間和一個私鑰,寫入cookie前都進行了MD5哈希。默認狀況下,私鑰名爲SpringSecured,但能夠經過.key(name)方法更改私鑰名稱。
退出功能是經過Servlet容器中的Filter實現的,這個Filter會攔截針對"/logout"的請求。當用戶訪問/logout時,這個請求會被Spring Security的Logout Filter處理。用戶會推出應用,全部的remember-me token都會被清除掉。在退出完成後,用戶瀏覽器將會重定向到/login?logout,從而容許用戶進行再次登錄。若但願用戶被重定向到其餘的頁面,可使用http.logout().logoutSucessUrl("url")中進行設置,也能夠調用.logoutUrl("url")來更改退出路徑。
使用JSP標籤庫,須要在JSP中聲明<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
Spring Security的JSP標籤庫只包含了三個標籤
<security:accesscontrollist> 若是用戶經過訪問控制列表授予了指定的權限,那麼渲染該標籤體中的內容
<security:authentication> 渲染當前用戶認證對象的詳細信息
<security:authorize> 若是用戶被授予了特定的權限或SpEL表達式的結果爲true,那麼渲染標籤體中的內容
使用<security:authentication>JSP標籤來訪問用戶的認證詳情
authorites:一組用於表示用戶所授予權限的GrantedAuthority對象
Credentials:用於覈實用戶的憑證(一般是用戶名和密碼)
details:認證的附加信息(IP地址,證件序列號,會話ID等)
principal:用戶的基本信息對象
e.g: Hello <security:authentication property="principal.username" var="loginId" scope="request" />
Spring Security的<security:authorize>JSP標籤可以根據用戶被授予的權限有條件的渲染頁面的部份內容
<sec:authorize access="hasRole('ROLE_SPITTER'')"> <s:url value="/spittles" var="spittle_url" /> <sf:form modelAttribute="spittle" action="${spittle_url}"> <sf:label path="text"><s:message code="lable.spittle" text="Enter spittle:" /></sf:label> <sf:textarea path="text" rows="2" cols="40" /> <sf:errors path="text" /> <br /> <div class="spitItSumbitIt"> <input type="submit" value="Spit it!" /> </div> </sf:form> </sec:authorize>
Thymeleaf的安全方言提供了與Spring Security標籤庫相對應的屬性。
1.須要在SpringTemplateEngine bean中聲明SpringSecurityDialect。
@Bean public SpringTemplateEngine templateEngine(TemplateResolver templateResolver){ SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver); templateEngine.addDialect(new SpringSecurityDialect()); return templateEngine; }
2.聲明命名空間
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
經常使用標籤:
sec:authentication 渲染認證對象的屬性,相似Spring Security的<sec:authentication/>JSP標籤
sec:authorize 基於表達式的計算結果,條件性的渲染內容,相似於Spring Security的<sec:authorize/>JSP標籤
sec:authorize-acl 基於表達式的計算結果,條件性渲染內容,相似於Spring Security的<sec:accesscontrollist/>JSP標籤
sec:authorize-expr sec:authorize屬性的別名
sec:authorize-url 基於給定URL路徑相關的安全規則,條件性的渲染內容,相似於Spring Security的<sec:authorize/>JSP標籤使用url屬性時的場景
Spring Security提供了三種不一樣的安全註解:
Spring Security自帶的@Secured註解
JSR-250的@RolesAllowed註解
表達式驅動的註解,包括@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter
@Secured和@RolesAllowed可以就有用戶所授予的權限限制對方法的訪問。當須要在方法上定義更加靈活的安全規則時,Spring Security提供了@PreAuthorize和@PostAuthorize,而@PreFilter/@PostFilter可以過濾方法返回的以上傳入方法的集合。
使用@Secured註解限制方法調用
import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; @Configuration @EnableGlobalMethodSecurity(securedEnabled = true) public class SecurityConfig extends GlobalMethodSecurityConfiguration{ }
在Spring中,若要啓用基於註解的方法安全性,關鍵要在配置類上使用@EnableGlobalMethodSecurity。若如今Web安全的配置類擴展了WebSecurityConfigurerAdapter,能夠重載GlobalMethodSecurityConfiguration的configure()方法。
@Secured註解會使用一個String數組做爲參數。每一個String值是一個權限,調用這個方法至少須要具有其中的一個權限。
@Secured告訴Spring,只有具備ROLE_SPITTER權限的認證用戶才能調用addSpittke()方法
@Secured("ROLE_SPITTER") public void addSpittle(Spittle spittle){ }