1. 用户名密码认证
用户名密码读取方式有三:
- 表单
- Basic认证
- Digest认证
存储机制:
- 内存存储
- JDBC存储
- 自定义存储 UserDetailsService
- LDAP存储
1.1 表单登录
用户是如何被重定向到登录表单的:
image.png
- 用户向/private发起未认证请求.
-
FilterSecurityInterceptor
通过抛出AccessDeniedException
表示拒绝这个请求. - 因为用户未认证,
ExceptionTranslationFilter
开始认证过程并通过AuthenticationEntryPoint
发送一个重定向到登录页面的响应. - 浏览器重定向到登录页面.
- 显示登录页面.
用户提交用户名密码后的处理过程:
image.png
- 当用户提交用户名密码后,
UserPasswordAuthenticationFilter
创建一个UsernamePasswordAuthenticationToken
(从请求中解析用户名密码) - token被传递给
AuthenticationManager
进行认证. - 后续过程同
AbstractAuthenticationProcessingFilter
(UserPasswordAuthenticationFilter继承自AbstractAuthenticationProcessingFilter
)
Spring Security 的表单登录功能默认开启, 但是如果提供了任何基于servlet的配置, 则需要显式配置:
// java
protected void configure(HttpSecurity http) {
http
// ...
.formLogin(withDefaults());
}
// xml
<http>
<form-login />
</http>
自定义登录页面
// java
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.formLogin(form -> form
.loginPage("/login")
.permitAll()
);
}
// xml
<http>
<intercept-url pattern="/login" access="permitAll" />
<form-login login-page="/login" />
</http>
登录表单
- 表单通过
/login
POST请求提交登录数据 - 表单需要包含一个CSRF token.
- 用户名参数名为 username
- 密码参数为 password
以上都可以自行配置, 如自定义登录页面, 则需要提供一个get方式的login请求, 以跳转到登录页面.
java配置见FormLoginConfigurer
. XML配置见<http>
1.2 Basic验证
Basic验证使用的是BasicAuthenticationEntryPoint
.
配置:
// java
protected void configure(HttpSecurity http) {
http
// ...
.httpBasic(withDefaults());
}
// xml
<http>
<http-basic />
</http>
1.3 UserDetailsService
UserDetailsService
通过使用DaoAuthenticationProvider
来获取用户名,密码及其他扩展信息.
1.4 PasswordEncoder
Spring Security加密支持. 常用实现类为BCryptPasswordEncoder
.
1.5 DaoAuthenticationProvider
AuthenticationProvider
的实现类, 通过UserDetailsService
和PasswordEncoder
支持验证用户名和密码.
- 读取用户名和密码到
UsernamePasswordAuthenticationToken
中, 并将其传递给ProviderManager. - ProviderManager选择调用DaoAuthenticationProvider.
- DaoAuthenticationProvider 通过UserDetailsService获取UserDetails.
- 调用PasswordEncoder验证密码.
- 如果验证成功, 返回UsernamePasswordAuthenticationToken(其principal为UserDetails对象). 并将其存储到SecurityContextHolder中.
2. Session Management
与Session相关的功能由SessionManagementFilter
和SessionAuthenticationStrategy
实现. 功能包括防止Session固定会话攻击, 检测session超时和限制session并发数.
2.1 检测超时
session失效后可以重定向到session无效页面.
<http>
<session-management invalid-session-url="/invalidSession.html"/>
</http>
如果使用以上配置检测session超时, 如果用户退出后又不关闭浏览器重新登录,可能会报错. 这是因为cookie没有被清除,即使用户已经注销,会话cookie也会被重新提交。因此需要在logout时显式地删除JSESSIONID cookie.
<http>
<logout delete-cookies="JSESSIONID" />
</http>
**注意: **如果在代理后面运行应用程序,那么还可以通过配置代理服务器来删除会话cookie。
2.2 session并发控制
限制用户的登录行为.
首先: 需要在web.xml中添加session事件监听器.
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
然后: 添加并发数量限制
// 当error-if-maximum-exceeded为false时, 第二次登录将会剔掉第一次登录.
// 如果为true, 第二次登录将会被拒绝. 如果是表单登录,则会进入认证失败页面, 如果是rememberme, 则会返回401.
<http>
<session-management>
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
</http>
2.3 Session固定保护
session固定会话攻击是利用服务器的session不变机制, 攻击者欺骗用户登录(此时登录所携带的sessionId为攻击者设置的sessionId), 用户登录后, 攻击者设置的sessionId对应的会话合法了, 然后就可以利用这个sessionId冒充用户以达到目的.
解决办法就是用户登录后就修改session信息.
Spring Security通过session-fixation-protection
属性控制session策略.
- none: 不做任何改变.
- newSession: 创建一个全新的session, 不会复制session数据, 但与spring security相关的属性会被复制到新session中.
- migrateSession: 创建一个新的session, 然后将旧session的数据复制到新的session中. servlet3.0及之前的默认策略.
- changeSessionId: 修改sessionId. servlet3.1及以后版本的默认策略, 这里使用的是servlet的固定会话攻击防护机制(HttpServletRequest#changeSessionId)
当发生session固定攻击时, spring 容器会发布一个SessionFixationProtectionEvent
. 如果使用changeSessionId机制, HttpSessionIdListener也会收到通知.
2.4 SessionManagementFilter
工作流程:
- 根据当前
SecurityContextHolder
的内容检查SecurityContextRepository
的内容,以确定用户在当前请求期间是否已通过身份验证. - 如果包含SecurityContext, 则不做任何事情.如果不包含, 但是当前的 SecurityContext中包含一个匿名的
Authentication
对象, 它则假设用户已被前面的filter进行验证过, 它将调用SessionAuthenticationStrategy
. - 如果当前用户没有经过身份验证,筛选器将检查是否请求了无效的会话ID(例如由于超时),如果设置了InvalidSessionStrategy,则将调用配置的InvalidSessionStrategy。最常见的行为就是重定向到一个固定的URL,这封装在标准实现SimpleRedirectInvalidSessionStrategy中。
2.5 SessionAuthenticationStrategy
它被SessionManagementFilter
和AbstractAuthenticationProcessingFilter
使用. 因此,如果使用定制的表单登录类,需要将它注入到这两个类中。
<http>
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>
<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
...
</beans:bean>
<beans:bean id="sas" class=
"org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />
2.6 并发控制
除了前面的配置, 还需要配置ConcurrentSessionFilter
到FilterChainProxy
. 它有两个参数: SessionRegistry, SessionInformationExpiredStrategy.
<http>
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>
<beans:bean id="redirectSessionInformationExpiredStrategy"
class="org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy">
<beans:constructor-arg name="invalidSessionUrl" value="/session-expired.htm" />
</beans:bean>
<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:constructor-arg name="sessionInformationExpiredStrategy" ref="redirectSessionInformationExpiredStrategy" />
</beans:bean>
<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<beans:constructor-arg>
<beans:list>
<beans:bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry"/>
<beans:property name="maximumSessions" value="1" />
<beans:property name="exceptionIfMaximumExceeded" value="true" />
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry"/>
</beans:bean>
</beans:list>
</beans:constructor-arg>
</beans:bean>
<beans:bean id="sessionRegistry"
class="org.springframework.security.core.session.SessionRegistryImpl" />
2.7. 类图
image.png3. Remeber-Me 认证
网站可以记住用户身份, 当用户再次访问网站时可自动登录. 这是通过cookie机制来实现的. Spring Security提供了两种实现: 一是基于cookie的实现, 二是持久存储的实现(数据库). 这两种实现都需要UserDetailsService
.
3.1 基于hash的token方式
在用户身份认证成功之后会发送一个cookie到浏览器. 其格式如下:
base64(username + ":" + expirationTime + ":" +
md5Hex(username + ":" + expirationTime + ":" password + ":" + key))
username: 用户名
password: 密码
expirationTime: token过期时间, 单位为毫秒
key: 防止remember-me令牌被修改的私钥
启用remeber-me
<http>
<remember-me key="myAppKey"/>
</http>
3.2 持久化token
使用这种方式需要提供数据源.
<http>
<remember-me data-source-ref="someDataSource"/>
</http>
数据库表建表SQL:
create table persistent_logins (username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null)
3.3 RemeberMe接口及实现
UsernamePasswordAuthenticationFilter
具有rememberme功能, 它是通过AbstractAuthenticationProcessingFilter
中的钩子实现, 这个钩子调用RememberMeServices
.
// RememberMeServices
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);
void loginFail(HttpServletRequest request, HttpServletResponse response);
void loginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication);
UsernamePasswordAuthenticationFilter只会调用loginFail
或loginSuccess
方法. autoLogin
是在RememberMeAuthenticationFilter
中调用.
PersistentTokenBasedRememberMeServices
PersistentTokenBasedRememberMeServices还需要一个UserDetailsService, 以获取用户信息进行验证并生成RemeberMeAuthenticationToken. 另外它还依赖PersistentTokenRepository
以用于存储token. 它默认有两种实现:
- InMemoryTokenRepositoryImpl
- JdbcTokenRepositoryImpl
3.4 RememberMeAuthenticationFilter类图
image.png4. 匿名认证
4.1 介绍
“匿名身份验证”的用户和未经身份验证的用户之间并没有真正的概念上的区别。匿名身份验证的好处是,所有URI模式都可以应用安全性, 还可保证SecutiryContextHolder总是包含一个Authentication对象而不是null.
4.2 配置
<http>
<anonymous/>
</http>
三个类提供了身份认证功能:
- AnonymousAuthenticationToken
用于存储为匿名用户授权的权限(GrantedAuthority) - AnonymousAuthenticationProvider
- AnonymousAuthenticationFilter
<bean id="anonymousAuthFilter"
class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
<property name="key" value="foobar"/>
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
</bean>
<bean id="anonymousAuthenticationProvider"
class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="foobar"/>
</bean>
key在provider和filter之间共享,
4.3 AuthenticationTrustResolver
4.4 spring mvc中获取匿名Authentication对象.
使用@CurrentSecurityContext
.
@GetMapping("/")
public String method(@CurrentSecurityContext SecurityContext context) {
return context.getAuthentication().getName();
}
网友评论