详解SpringSecurity认证(下)

简介: 详解SpringSecurity认证(下)

补充前后端分离实现


配置登录成功


package security.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        Map<String, Object> result = new HashMap<>();
        result.put("msg", "登录成功!");
        result.put("status", 200);
        response.setContentType("application/json;charset=UTF-8");
        String s = new ObjectMapper().writeValueAsString(result);
        response.getWriter().println(s);
    }
}

登录失败


package security.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class MyLogout implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        Map<String, Object> result = new HashMap<>();
        result.put("msg", "注销成功!");
        result.put("status", 500);
        response.setContentType("application/json;charset=UTF-8");
        String s = new ObjectMapper().writeValueAsString(result);
        response.getWriter().println(s);
    }


前端Thymeleaf整合security实现


外部资源: xmlns:sec=”http://www.thymeleaf.org/extras/spring-security


<!DOCTYPE html>
<html lang="en" xmlns:sec="http://www.thymeleaf.org/extras/spring-security" xmlns:th="http://www.thymeleaf.otg">
<head>
    <meta charset="UTF-8">
    <title>用户界面</title>
</head>
<body>
<h2>获取用户详细信息</h2>
<ul>
  <li sec:authentication="principal.username"></li>
  <li sec:authentication="principal.authorities"></li>
  <li sec:authentication="principal.accountNonExpired"></li>
  <li sec:authentication="principal.accountNonLocked"></li>
  <li sec:authentication="principal.credentialsNonExpired"></li>
</ul>
</body>
</html>


重写UserDetailsService


@Component
public class MyUserDetailsService implements UserDetailsService {
    private final UserDao userDao;
    @Autowired
    public MyUserDetailsService(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userDao.loadUserByUsername(username);
        if (ObjectUtils.isEmpty(user)) throw new UsernameNotFoundException("用户名不正确!");
        //存在的话 , 赋予权限信息
        List<Role> roles = userDao.getRolesByUid(user.getId());
        user.setRoles(roles);
        return user;
    }
}


实现Dao层接口及其mapper


Dao


@Mapper
public interface UserDao {
    //根据用户名查询user
    User loadUserByUsername(String username);
    //根据用户id查询角色
    List<Role> getRolesByUid(Integer uid);
}

mapper


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="security.dao.UserDao">
    <!--查询单个loadUserByUsername-->
    <select id="loadUserByUsername" resultType="User">
        select id,
               username,
               password,
               enabled,
               accountNonExpired,
               accountNonLocked,
               credentialsNonExpired
        from user
        where username = #{username}
    </select>
    <!--查询指定行数据 ,表连接-->
    <select id="getRolesByUid" resultType="Role">
        select r.id,
               r.name,
               r.name_zh nameZh
        from role r,
             user_role ur
        where r.id = ur.rid
          and ur.uid = #{uid}
    </select>
</mapper>


多表联查新思路


认证002.png


用户登录数据获取 (SecurityContextHolder)



Spring Security 会将登录用户数据保存在 Session 中。但是,为了使用方便,Spring Security在此基础上还做了一些改进,其中最主要的一个变化就是线程绑定。当用户登录成功后,Spring Security 会将登录成功的用户信息保存到 SecurityContextHolder 中。


SecurityContextHolder 中的数据保存默认是通过ThreadLocal 来实现的,使用 ThreadLocal 创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是用户数据和请求线程绑定在一起。当登录请求处理完毕后,Spring Security 会将 SecurityContextHolder 中的数据拿出来保存到 Session 中,同时将 SecurityContexHolder 中的数据清空。以后每当有请求到来时,Spring Security 就会先从 Session 中取出用户登录数据,保存到SecurityContextHolder 中,方便在该请求的后续处理过程中使用,同时在请求结束时将 SecurityContextHolder 中的数据拿出来保存到 Session 中,然后将SecurityContextHolder 中的数据清空。


实际上 SecurityContextHolder 中存储是 SecurityContext,在 SecurityContext 中存储是 Authentication。


认证003.png


1.MODE THREADLOCAL:这种存放策略是将 SecurityContext 存放在 ThreadLocal中,大家知道 Threadlocal 的特点是在哪个线程中存储就要在哪个线程中读取,这其实非常适合 web 应用,因为在默认情况下,一个请求无论经过多少 Filter 到达 Servlet,都是由一个线程来处理的。这也是 SecurityContextHolder 的默认存储策略,这种存储策略意味着如果在具体的业务处理代码中,开启了子线程,在子线程中去获取登录用户数据,就会获取不到。

2.MODE INHERITABLETHREADLOCAL:这种存储模式适用于多线程环境,如果希望在子线程中也能够获取到登录用户数据,那么可以使用这种存储模式。

3.MODE GLOBAL:这种存储模式实际上是将数据保存在一个静态变量中,在 JavaWeb开发中,这种模式很少使用到。


自定义认证数据源


认证004.png


  • 发起认证请求,请求中携带用户名、密码,该请求会被UsernamePasswordAuthenticationFilter 拦截
  • 在UsernamePasswordAuthenticationFilter的attemptAuthentication方法中将请求中用户名和密码,封装为Authentication对象,并交给AuthenticationManager 进行认证
  • 认证成功,将认证信息存储到 SecurityContextHodler 以及调用记住我等,并回调 AuthenticationSuccessHandler 处理
  • 认证失败,清除 SecurityContextHodler 以及 记住我中信息,回调 AuthenticationFailureHandler 处理


三者关系



从上面分析中得知,AuthenticationManager 是认证的核心类,但实际上在底层真正认证时还离不开 ProviderManager 以及 AuthenticationProvider 。他们三者关系是样的呢?


  • AuthenticationManager 是一个认证管理器,它定义了 Spring Security 过滤器要执行认证操作。
  • ProviderManager AuthenticationManager接口的实现类。Spring Security 认证时默认使用就是 ProviderManager。
  • AuthenticationProvider 就是针对不同的身份类型执行的具体的身份认证。


Spring Seourity 中,允许系统同时支持多种不同的认证方式,例如同时支持用户名/密码认证、ReremberMe 认证、手机号码动态认证等,而不同的认证方式对应了不同的 AuthenticationProvider,所以一个完整的认证流程可能由多个 AuthenticationProvider 来提供。


多个 AuthenticationProvider 将组成一个列表,这个列表将由 ProviderManager 代理。换句话说,在ProviderManager 中存在一个 AuthenticationProvider 列表,在Provider Manager 中遍历列表中的每一个 AuthenticationProvider 去执行身份认证,最终得到认证结果。


ProviderManager 本身也可以再配置一个 AuthenticationManager 作为 parent,这样当当前ProviderManager 认证失败之后,就可以进入到 parent 中再次进行认证。理论上来说,ProviderManager 的 parent 可以是任意类型的 AuthenticationManager,但是通常都是由ProviderManager 来扮演 parent 的角色,也就是 ProviderManager 是 ProviderManager 的 parent。(自己多理解几遍)


ProviderManager本身也可以有多个,多个ProviderManager 共用同一个 parent。有时,一个应用程序有受保护资源的逻辑组(例如,所有符合路径模式的网络资源,如/api/**),每个组可以有自己的专用 AuthenticationManager。通常,每个组都是一个ProviderManager,它们共享一个父级。然后,父级是一种 全局资源,作为所有提供者的后备资源。


根据上面的介绍,我们绘出新的 AuthenticationManager、ProvideManager 和 AuthentictionProvider 关系


认证005.png


数据源的获取。默认情况下 AuthenticationProvider 是由 DaoAuthenticationProvider 类来实现认证的,在DaoAuthenticationProvider 认证时又通过 UserDetailsService 完成数据源的校验。


总结: AuthenticationManager 是认证管理器,在 Spring Security 中有全局AuthenticationManager,也可以有局部AuthenticationManager。


  • 全局的AuthenticationManager用来对全x局认证进行处理
  • 局部的AuthenticationManager用来对某些特殊资源认证处理。


当然无论是全局认证管理器还是局部认证管理器都是由 ProviderManger 进行实现。 每一个ProviderManger中都代理一个AuthenticationProvider的列表,列表中每一个实现代表一种身份认证方式。认证时底层数据源需要调用 UserDetailService 来实现。


配置全局 AuthenticationManager


默认的全局 AuthenticationManager


@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
  @Autowired
  public void initialize(AuthenticationManagerBuilder builder) {
    //builder..
  }
}
  • springboot 对 security 进行自动配置时自动在工厂中创建一个全局AuthenticationManager


总结


  1. 默认自动配置创建全局AuthenticationManager 默认找当前项目中是否存在自定义 UserDetailService 实例 自动将当前项目 UserDetailService 实例设置为数据源
  2. 默认自动配置创建全局AuthenticationManager 在工厂中使用时直接在代码中注入即可
  3. 自定义全局 AuthenticationManager


@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
  @Override
  public void configure(AuthenticationManagerBuilder builder) {
    //builder ....
  }
}


总结


  1. 一旦通过 configure 方法自定义 AuthenticationManager实现 就回将工厂中自动配置AuthenticationManager 进行覆盖
  2. 一旦通过 configure 方法自定义 AuthenticationManager实现 需要在实现中指定认证数据源对象 UserDetaiService 实例
  3. 一旦通过 configure 方法自定义 AuthenticationManager实现 这种方式创建AuthenticationManager对象工厂内部本地一个 AuthenticationManager 对象 不允许在其他自定义组件中进行注入


用来在工厂中暴露自定义AuthenticationManager 实例


@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
    //1.自定义AuthenticationManager  推荐  并没有在工厂中暴露出来
    @Override
    public void configure(AuthenticationManagerBuilder builder) throws Exception {
        System.out.println("自定义AuthenticationManager: " + builder);
        builder.userDetailsService(userDetailsService());
    }
    //作用: 用来将自定义AuthenticationManager在工厂中进行暴露,可以在任何位置注入
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}


文献参考



编程不良人

深入浅出Spring Security (王松)

目录
相关文章
|
安全 Java 数据安全/隐私保护
SpringSecurity 认证流程
通过了解SpringSecurity核心组件后,就可以进一步了解其认证的实现流程了。
108 0
|
6月前
【SpringSecurity 】SpringSecurity 自定义登录页面
【SpringSecurity 】SpringSecurity 自定义登录页面
105 0
|
6月前
|
安全 搜索推荐 Java
【SpringSecurity6.x】会话管理
【SpringSecurity6.x】会话管理
83 0
|
6月前
|
缓存 安全 数据安全/隐私保护
Shiro - 认证那些事
Shiro - 认证那些事
43 0
|
安全 Java 数据库
Spring Security详细讲解(JWT+SpringSecurity登入案例)
通过本篇博文,你可以详细了解Spring Security的相关概念与原理,并且掌握Spring Security的认证与授权,通过博文中的登入案例可以让自己定制去Spring Security认证授权方案。
Spring Security详细讲解(JWT+SpringSecurity登入案例)
|
安全 前端开发 Java
详解SpringSecurity认证(上)
详解SpringSecurity认证(上)
163 0
|
NoSQL Java 数据库连接
SpringSecurity -- 授权-OAuth2 --
针对之前的授权做个补充,这里集成OAuth2来实现 目前支持5中方式,inMemory,jdbc,redis,jwt,jwk......
120 0
|
JSON 前端开发 数据格式
六.SpringSecurity基础-认证授权结果处理
SpringSecurity基础-认证授权结果处理
|
JSON 前端开发 数据格式
SpringSecurity基础-认证授权结果处理
在传统的应用中,认证成功后页面需要跳转到认证成功页面或者跳转到个人中心页,但是在前后端分离的项目通常是使用Ajax请求完成认证,这时候我们需要返回一个JSON结果告知前端认证结果,然后前端自行跳转页面。 要做到上述功能,我们需要自定义认证成功处理器实现AuthenticationSuccessHandler接口复写 onAuthenticationSuccess方法,该方法其中一个参数是Authentication ,他里面封装了认证信息,用户信息UserDetails等,我们需要在这个方法中使用Response写出json数据即可
139 0
|
存储 安全 Java
SpringSecurity基础-简单登录实现
1.SpringSecurity介绍 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
96 0