美文网首页
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