美文网首页
Spring-Security-文档笔记之认证机制

Spring-Security-文档笔记之认证机制

作者: xzz4632 | 来源:发表于2021-08-05 00:44 被阅读0次

1. 用户名密码认证

用户名密码读取方式有三:

  • 表单
  • Basic认证
  • Digest认证

存储机制:

  • 内存存储
  • JDBC存储
  • 自定义存储 UserDetailsService
  • LDAP存储
1.1 表单登录

用户是如何被重定向到登录表单的:


image.png
  1. 用户向/private发起未认证请求.
  2. FilterSecurityInterceptor通过抛出AccessDeniedException表示拒绝这个请求.
  3. 因为用户未认证, ExceptionTranslationFilter开始认证过程并通过AuthenticationEntryPoint发送一个重定向到登录页面的响应.
  4. 浏览器重定向到登录页面.
  5. 显示登录页面.

用户提交用户名密码后的处理过程:


image.png
  1. 当用户提交用户名密码后, UserPasswordAuthenticationFilter创建一个UsernamePasswordAuthenticationToken(从请求中解析用户名密码)
  2. token被传递给AuthenticationManager进行认证.
  3. 后续过程同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的实现类, 通过UserDetailsServicePasswordEncoder支持验证用户名和密码.

image.png
  1. 读取用户名和密码到UsernamePasswordAuthenticationToken中, 并将其传递给ProviderManager.
  2. ProviderManager选择调用DaoAuthenticationProvider.
  3. DaoAuthenticationProvider 通过UserDetailsService获取UserDetails.
  4. 调用PasswordEncoder验证密码.
  5. 如果验证成功, 返回UsernamePasswordAuthenticationToken(其principal为UserDetails对象). 并将其存储到SecurityContextHolder中.

2. Session Management

与Session相关的功能由SessionManagementFilterSessionAuthenticationStrategy实现. 功能包括防止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

工作流程:

  1. 根据当前SecurityContextHolder的内容检查SecurityContextRepository的内容,以确定用户在当前请求期间是否已通过身份验证.
  2. 如果包含SecurityContext, 则不做任何事情.如果不包含, 但是当前的 SecurityContext中包含一个匿名的Authentication对象, 它则假设用户已被前面的filter进行验证过, 它将调用SessionAuthenticationStrategy.
  3. 如果当前用户没有经过身份验证,筛选器将检查是否请求了无效的会话ID(例如由于超时),如果设置了InvalidSessionStrategy,则将调用配置的InvalidSessionStrategy。最常见的行为就是重定向到一个固定的URL,这封装在标准实现SimpleRedirectInvalidSessionStrategy中。
2.5 SessionAuthenticationStrategy

它被SessionManagementFilterAbstractAuthenticationProcessingFilter使用. 因此,如果使用定制的表单登录类,需要将它注入到这两个类中。

<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 并发控制

除了前面的配置, 还需要配置ConcurrentSessionFilterFilterChainProxy. 它有两个参数: 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.png

3. 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只会调用loginFailloginSuccess方法. autoLogin是在RememberMeAuthenticationFilter中调用.

PersistentTokenBasedRememberMeServices
PersistentTokenBasedRememberMeServices还需要一个UserDetailsService, 以获取用户信息进行验证并生成RemeberMeAuthenticationToken. 另外它还依赖PersistentTokenRepository以用于存储token. 它默认有两种实现:

  • InMemoryTokenRepositoryImpl
  • JdbcTokenRepositoryImpl
3.4 RememberMeAuthenticationFilter类图
image.png

4. 匿名认证

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();
}

相关文章

网友评论

      本文标题:Spring-Security-文档笔记之认证机制

      本文链接:https://www.haomeiwen.com/subject/jjdpvltx.html