Spring Security学习笔记
这篇文章是在学习Spring Security 4.1.3.RELEASE版本是做的学习笔记,相当于是一边看一边记录吧。以前使用过一段时间Spring Security,但是发现过一段时间之后,以前看的用的东西基本上全部还回去了,这次看这个吧就想着在这上面先把自己看的内容纪录一下吧,为自己后面使用做一个参考吧。
学习地址:http://docs.spring.io/spring-security/site/docs/4.1.3.RELEASE/reference/htmlsingle/#what-is-acegi-security
2.4 获取Spring Security
2.4.1 通过在Spring Security官网上面去下载自己需要的jar包,这里就不贴出具体的地址了,上Spring.io就可以找到的。
2.4.2 通过maven方式获取:这里还有一些快照版本以及其他的一些配置,这里不记录了。
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
</dependencies>
2.4.3 通过grandle方式:
dependencies {
compile 'org.springframework.security:spring-security-web:4.1.3.RELEASE'
compile 'org.springframework.security:spring-security-config:4.1.3.RELEASE'
}
2.4.4 工程模块的讲解
在Spring Security3.0版本之后开始分离了很多个模块,这样我们可以通过自己的需要来添加需要的jar包,下面来讲解每个分割开的模块的具体用处吧。
- Core - spring-security-core.jar 这个jar包不多说了,要使用Spring Security就必须引入这个jar包,没商量。这个jar包可以应用在单独的应用中,远程客户端调用,Service调用以及数据库层的调用等等,包含下面的package:
org.springframework.security.core
org.springframework.security.access
org.springframework.security.authentication
org.springframework.security.provisioning
- Remoting - spring-security-remoting.jar 这个jar包在集成Spring Remote的时候要使用到,或者在有remote client情景的时候需要到这个jar包。
- Web - spring-security-web.jar 这个jar包在创建web应用的时候需要引入。
- Config - spring-security-config.jar 这个jar包是一个配置支持包,主要在使用Spring Security的配置(包含XML或者java configuration)时需要。
- LDAP - spring-security-ldap.jar 在需要使用LDAP认证的时候需要这个jar包。
- ACL - spring-security-acl.jar 在创建domain object实例的时候需要使用这个jar包。
- CAS - spring-security-cas.jar 这个jar的意思不是很明白,好像是说是使用单点登录的时候会用到这个jar包。
- OpenID - spring-security-openid.jar
- Test - spring-security-test.jar
到这里基本上所有的jar包都介绍完了,个人认为在一般简单的web应用中只需要将core,web,config这几个jar包引入基本上就可以使用简单的Spring Security了。
5 java configuration
<span style="font-size:18px;">import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}</span>
值得注意的是:AuthenticationManagerBuilder类与@EnableWebSecurity。
- 认证应用中所有的url
- 自动产生一个登陆页面
- 可以通过“user”与"password"进行登陆
- 允许登出更能
- 阻止CSRF攻击
- session的保护
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(WebSecurityConfig.class);
}
}
SecurityWebApplicationInitializer这个类主要完成对springSecurityFilterChain的注册以及使用classLoaderListener加载SecurityWebConfig类。
<span style="font-size:18px;">import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
}</span>
这里我们主要完成了对springSecurityFilterChain的注册,但是SecurityWebConfig并没有被加载。这里我们可以在WebApplicationInitializer中来加载SecurityWebConfig类的配置信息:
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { WebSecurityConfig.class };
}
// ... other overrides ...
}
好了,这里基本上记录了如何通过java 文件来配置Spring Security。
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic();
}
从这个方法的实现了如下功能:
- 应用中所有的url都必须user进行认证
- user只有通过登录进行认证
- 允许用户通过基本的http认证
<http>
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
</http>
5.4 使用自定义的登录页面
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login") 1
.permitAll(); 2
}
标识‘1’表示使用自定义的登录页面;标识‘2’表示允许所有用户都可以访问登陆页面。
<span style="color: rgb(111, 111, 111);"><c:url value="/login" var="loginUrl"/>
<form action="${loginUrl}" method="post"> 1
<c:if test="${param.error != null}"> 2
<p>
Invalid username and password.
</p>
</c:if>
<c:if test="${param.logout != null}"> 3
<p>
You have been logged out.
</p>
</c:if>
<p>
<label for="username">Username</label>
<input type="text" id="username" name="username"/> 4
</p>
<p>
<label for="password">Password</label>
<input type="password" id="password" name="password"/> 5
</p>
<input type="hidden" 6
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
<button type="submit" class="btn">Log in<</span><span style="color:#330033;">/</span><span style="color:#6f6f6f;">button>
</form></span>
- 标识‘1’:登录成功即将跳转的页面
- 标识‘2’:没有通过登录认证时的错误信息提示
- 表示‘3’:登出功能提示信息
- 标识‘4’:登录username
- 标识‘5’:登录password
- 标识‘6’:crsf相关配置信息,后续讲解吧。
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() 1
.antMatchers("/resources/**", "/signup", "/about").permitAll() 2
.antMatchers("/admin/**").hasRole("ADMIN") 3
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") 4
.anyRequest().authenticated() 5
.and()
// ...
.formLogin();
}
标识‘2’表示所有通过“/resources/**”,"/signup","/about"开始的request url将通过验证;
- 清除Http Session中的内容
- 清除RememberMe
- clearing the SecurityContextHolder
- 跳转到/login?logout
protected void configure(HttpSecurity http) throws Exception {
http
.logout() 1
.logoutUrl("/my/logout") 2
.logoutSuccessUrl("/my/index") 3
.logoutSuccessHandler(logoutSuccessHandler) 4
.invalidateHttpSession(true) 5
.addLogoutHandler(logoutHandler) 6
.deleteCookies(cookieNamesToClear) 7
.and()
...
}
PersistentTokenBasedRememberMeServices
TokenBasedRememberMeServices
CookieClearingLogoutHandler
CsrfLogoutHandler
SecurityContextLogoutHandler
这些实现了这个LogoutHandler接口的类是一些快速处理对应内容的handler,比如清理cookie的时候可以使用CookieClearingLogoutHandler。
<span style="color:#6d180b;">@Autowired
private DataSource dataSource;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
</span><span style="color:#ff0000;">.dataSource(dataSource)</span><span style="color:#6d180b;">
.withDefaultSchema()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}</span>
5.6.4 AuthenticationProvider
@Bean
public SpringAuthenticationProvider springAuthenticationProvider() {
return new SpringAuthenticationProvider();
}
@Bean
public SpringDataUserDetailsService springDataUserDetailsService() {
return new SpringDataUserDetailsService();
}
当然我们也可以自定义password的编码方式,通过实现PassWordEncoder。例如:
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@EnableWebSecurity
public class MultiHttpSecurityConfig {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) { 1
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
@Configuration
@Order(1) 2
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**") 3
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}
@Configuration 4
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
}
标识'1'表示配置一个全局的认证;
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
然后在service层我们就可以做如下操作:
public interface BankService {
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();
@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}
这样在使用具体的方法的时候就必须先通过认证。也就是对于post(Account account,double amount)方法只有在拥有'ROLE_TELLER'
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig {
// ...
}
另外我们还可以通过如下进行方法的配置:
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}
具体的使用可以通过:
public interface BankService {
@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);
@PreAuthorize("isAnonymous()")
public Account[] findAccounts();
@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
// ... create and return custom MethodSecurityExpressionHandler ...
return expressionHandler;
}
}
6.1 Introduction
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
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-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
...
</beans>
这个是要使用到Spring Security的时候我们需要将对应的shcema添加到xml文件中。
<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-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
...
</beans:beans>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<http>
<intercept-url pattern="/**" access="hasRole('USER')" />
<form-login />
<logout />
</http>
在测试项目中,我们只需要在Spring Security的配置文件中加入这几行代码,便可以来进行简单的使用了。
- 所有的Url只有拥有‘USER’角色的用户才能访问到
- 只能通过'username','password'进行登录
- 拥有登出功能
<authentication-manager>
<authentication-provider>
<user-service>
<user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="bobspassword" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
在这里<authentication-provider>将会为我们创建DaoAuthenticationProvider的实例,<user-service>将创建出
<span style="font-size:18px;"><http>
<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/**" access="ROLE_USER" /> 2
<form-login login-page='/login.jsp'/>
</http></span>
就像这样,<form-login>中为我们指定了一个login.jsp页面,这个就将使用到我们自己的登录页面了。
<span style="font-size:18px;"><http pattern="/css/**" security="none"/>
<http pattern="/login.jsp*" security="none"/>
<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
</http></span>
通过这个配置,我们的login.jsp文件将不再受Security的验证,保证所有的用户都能够访问我们的登录页面。
<span style="font-size:18px;"><authentication-manager>
<authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager></span>
这中的myUserDetailsService是MyUserDetailsService注入在Spring中的bean的名字,并且MyUserDetialsService这个类实现了UserDetailsService这个接口。接下来用户的信息我们只要通过MyUserDetailsService这个类来获取便可。
<authentication-manager>
<authentication-provider>
<jdbc-user-service data-source-ref="securityDataSource"/>
</authentication-provider>
</authentication-manager>
这中的securityDataSource是applicationContext中关于DataSource配置的bean的名字,当然这里面要有我们需要的存储用户信息的表。
<authentication-manager>
<authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>
<beans:bean id="myUserDetailsService"
class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource"/>
</beans:bean>
当然,我们可以自定义自己需要的Authentication Provider来完成自己的需求,具体配置如下
<authentication-manager>
<authentication-provider ref='myAuthenticationProvider'/>
</authentication-manager>
这里的myAuthenticationProvider表示一个实现了AuthenticationProvider的类的bean的名称。
<beans:bean name="bcryptEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<authentication-manager>
<authentication-provider>
<password-encoder ref="bcryptEncoder"/>
<user-service>
<user name="jimi" password="d7e6351eaa13189a5a3641bab846c8e8c69ba39f"
authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="4e7421b1b8765d8f9406d87e7cc6aa784c4ab97f"
authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
<http>
<intercept-url pattern="/secure/**" access="ROLE_USER" requires-channel="https"/>
<intercept-url pattern="/**" access="ROLE_USER" requires-channel="any"/>
...
</http>
另外,我们还可以配置自己需要的端口,
<http>
...
<port-mappings>
<port-mapping http="9080" https="9443"/>
</port-mappings>
</http>
<http>
...
<session-management invalid-session-url="/invalidSession.htm" />
</http>
这个时候我们还需要删除掉客户端的cookie,如下:
<http>
<logout delete-cookies="JSESSIONID" />
</http>
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
然后再Spring Security配置文件中添加
<http>
...
<session-management>
<concurrency-control max-sessions="1" />
</session-management>
</http>
这样用户在进行第二次登录的时候将会被阻止,当然我们也可以配置一下让用户在第二次登录的时候跳转到指定的page页面或给予提示信息
<http>
...
<session-management>
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
</http>
这样,如果用户在已经登录一次之后将会使
到
authentication-failure-url
指定的url
。或者我们可以在session-management元素中指定session-authentication-error-url
- none 什么都不做
- newSession 每次登陆都创建一个新的session,不会拷贝一个已有的session的信息
- migrateSession 每次登录都创建一个新的session,但是会拷贝以前session中的信息到新session中,这是Spring Security默认配置
- changeSessionId 不会创建新的session,使用servlet中提供的session的保护方式,这个只有在servlet3.1之后才有,之前的版本会抛出异常