Spring Security -1登录流程

Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。

1.环境搭建

1. 引入Spring Security依赖
2.当前项目中的所有资源都被springSecurity管理

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

 Spring Security非常重要的配置类,一定要有,不然下面的流程都无法进行

/**
 * spring security配置
 */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

     /**
     * token认证过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 解决 无法直接注入 AuthenticationManager,只有配置了这个bean,登录接口那里才能用
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

/**过滤器,过滤登录请求不拦截*/
     @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // CSRF禁用,因为不使用session
                .csrf().disable()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 验证码captchaImage 允许匿名访问
                .antMatchers("/login").anonymous()
                //放行前端
                .antMatchers(
                        HttpMethod.GET,"/**/*.html",
                        "/**/*.css", "/**/*.js").permitAll()
         // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        // 添加CORS filter
        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
       }

/**
     * 强散列哈希加密实现,只有配置了这个Bean,验证密码的时候才能指定使用的是BCrypt加盐加密
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }

}

 返回的UserDetails的实现类

@Data
public class LoginUser implements UserDetails
{
    /**
     * 用户唯一标识
     */
    private String token;
    /**
     * 用户信息
     */
    private User user;

    public LoginUser()
    {
    }
    public LoginUser(SysUser user, Set<String> permissions)
    {
        this.user = user;
        this.permissions = permissions;
    }
}

②user实体类

@Data
public class SysUser extends BaseEntity
{

    /** 用户ID */
    private Long userId;
    /** 用户账号 */
    private String userName;
    /** 密码 */
    private String password;

    public SysUser()
    {
    }

    public SysUser(Long userId)
    {
        this.userId = userId;
    }

前端发送请求给我们的登录接口

@Component
public class SysLoginService {
 @Autowired
    private RedisCache redisCache;

 public String login(String username, String password, String code, String uuid) {
        // 用户验证
        /**
         * !!!看我看我看我!!!
         *看我少走弯路,获取用户对象的时候,会去调用下面的这个方法查询用户对象
         * 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
         */
        System.out.println("username "+username+" -----password "+password);
        Authentication authentication = authenticationManager
        .authenticate(new UsernamePasswordAuthenticationToken(username, password));
        
        //判断是否为空,如果为空则代表登陆失败验证错误
        if(Object.isNull(authentication)){
         有写自定义异常就抛异常,没有就输出控制台   
        }
        
        User User = (User) authentication.getPrincipal();
        // 生成token
        return tokenService.createToken(loginUser);
    }

 重写的UserDetailsServiceImpl实现类

/**
 * 用户验证处理
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    /**这个查询数据库用户表的业务层持久层自己写,太简单我就不写了,文章重点不在这*/
    @Autowired
    private ISysUserService userService;

/**此处重写了使用的loadUserByUsername方法,这样就不会走默认的查询内存的方法
*/
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
           //查询此用户名的数据库信息
        SysUser user = userService.selectByUserName(username);

        if (StringUtils.isNull(user)) {
               写了自定义异常就在这里抛出异常(用户不存在),没写就写个输出语句输出控制台
        }
        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
             写了自定义异常就在这里抛出异常(用户已被删除),没写就写个输出语句输出控制台
        }
        return new LoginUser(user,null);
    }
}

以及redis的代码,登录查询用户表的业务层和持久层等,这些大都一样,我就不写了

2. 底层原理

 在项目资源前,添加了一个过滤器链内部包含了提供各种功能的过滤器

3. 核心过滤器

1负责身份验证的过滤器
UsernamePasswordAuthenticationFilter

2.负责权限校验的过滤器(Spring Security另一功能鉴权使用的)
FilterSecurityInterceptor


Spring Security登录的大致流程

前端携带账号密码验证码发送请求,服务器与数据库中的对比如果正确就生成一个jwt返回给前端

具体流程请看这张图

 前端的请求会发送到UsernamePasswordAuthenticationFilter类上,然后封装成Authentication对象,此时还没有权限,然后将对象依次向后发送,我们只需要关注最后一个类

 Spring Security的UserDetailService接口类默认会从内存中查找数据,我们需要重写这个接口来实现从数据库中查找数据.


 前端发送请求给登录接口,登录接口会将数据传给经过重写的UserDetailsServiceImpl类,

UserDetailsServiceImpl类将查询到的用户数据封装返回给负责身份验证的过滤器
UsernamePasswordAuthenticationFilter

这个过滤器不需要我们写,是jar包自带的,它会自动帮我们判断密码是否正确,这里因为我们在SecurityConfig配置了使用BCrypt加盐加密(这里我建议创建账号的时候密码的加密方式使用Bcript加盐加密,这种加密方式是不可逆不宜破解的,安全性较高,每次生成的加密字段都不相同)

如果密码正确,那么将生成JWT(JWT是token具体的一种实现方式,本质上就是一个字符串,可以理解为一种支持集群/分布式/微服务架构的一种session,他不存储在服务器,而是存储在客户端)


生成JWT我们可以使用JJWT工具,导入pom依赖(jwt可以说就是token)

<!--Token生成与解析-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>

编写一个Token类来使用这个依赖,类中使用redis,是因为Spring Security的另一功能鉴权需要使用数据,所以存入Redis中以备使用

@Component
public class TokenService {

    @Autowired
    private RedisCache redisCache;

     /**
     * 创建令牌
     * @param loginUser 用户信息
     * @return 令牌
     */
    public String createToken(User user) {
        String token = user.getId;
        loginUser.setToken(token);
        setUserAgent(user);
        refreshToken(user);

        Map<String, Object> claims = new HashMap<>();
        claims.put("login_user_key", token);
        //存放非敏感信息
        claims.put("username",loginUser.getUsername());
        return createToken(claims);
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private String createToken(Map<String, Object> claims)
    {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
    }

  登录接口的最后一点生成token使用的就是这个Token类生成的token

至此,使用Spring Security 的登录操作就结束了,token是Spring Security的另一大功能,鉴权索要使用的,及登陆完成后进行其他操作时鉴权所使用的数据

以上就是我对Spring Security的登录操作的个人理解,本人尚且才疏学浅,描述的不是很详细,如果各位想了解的更详细可以去看B站的三更草堂的视频.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值