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

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();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 在Web应用程序中的身份验证 现在让我们来看看你在Web应用程序中使用Spring Security的情况(不启用...
    kuisasa阅读 1,246评论 0 1
  • 原理Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring...
    MlLance阅读 563评论 2 1
  • 本文基于 Spring Security 5.3版本官方文档 一、简介 Spring Security是一个提供身...
    慵懒的阳光丶阅读 593评论 0 0
  • Spring Security 是一个基于 Spring AOP 和 Servlet 过滤器的安全框架,它提供了安...
    聪明的奇瑞阅读 17,046评论 3 38
  • 简介 Spring Security:是一个提供身份验证,授权和保护以防止常见攻击的框架。 凭借对命令式和反应式应...
    呼呼菜菜阅读 3,743评论 1 7