sprinboot_springSecurity_vue

 一、登录功能开发

前期准备:

编写SecurityConfig配置类,其中包括注入密码加密器、认证管理器、过滤器链、

 //注入过滤链
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        //关闭csrf()攻击防护
        http.csrf().disable();
        //允许跨域
        http.cors();
        //关闭iframe窗口防护
        http.headers().frameOptions().disable();
        //关闭session会话
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //配置认证过滤器
        http.addFilterAfter(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);
        //配置所有请求必须认证
        http.authorizeRequests().anyRequest().authenticated();
        //配置认证失败处理
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        return http.build();
    }

DaoAuthenticationProvider

  // 注入DaoAuthenticationProvider
    @Bean
    public DaoAuthenticationProvider authenticationProvider(){
        DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
        auth.setUserDetailsService(userDetailsService);
        
        auth.setPasswordEncoder(passwordEncoder());
        return auth;
    }

,配置忽略路径

//配置忽略路径
    @Bean
    public WebSecurityCustomizer securityCustomizer() throws Exception{
        return (web) -> {
            web.ignoring().antMatchers("/api/captcha",
                    "/api/login",
                    "/doc.html",
                    "/webjars/**",
                    "/swagger-resources/**",
                    "/v2/api-docs/**");
        };
    }

一.后端:

        1.验证码实现:

                (1)使用CaptchaUtil工具类创建一个验证码图片

                (2)获取验证码内容(code)和验证码ID,以前缀+ID为key,码为值存入到redis数据库中

                (3)向前端响应数据,包括验证码ID和getImageBase64Data()方法生成的带前缀的验证码图片压缩的字符串。

 public Result captcha(){
        //1.获取验证码
        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 40, 4, 20);
        //获取验证码压缩图片
        String imageBase64Data = captcha.getImageBase64Data();
        //获取验证码内容
        String captchaCode = captcha.getCode();
        //获取验证码id
        String captchaId = UUID.randomUUID().toString();
        //2.将验证码信息存到redis中 key 为wy:login:captcha:+id redis中会以冒号为分割创建文件夹
        redisTemplate.opsForValue().set(RedisConstant.CAPTCHA_PRE+captchaId,
                captchaCode,RedisConstant.CAPTCHA_EXPIRE_TIME, TimeUnit.SECONDS);

        //3.响应数据
        HashMap<String,Object> map = new HashMap<>();
        map.put("captchaId",captchaId);
        map.put("imageBase64",imageBase64Data);
        return Result.success(map);
    }

        2.jwt认证过滤器

              (1)继承OncePerRequestFilter类,重写doFilterInternal()方法

                (2) 从请求头中获取token,如果没有token(未登录)放行,有就继续

              (3)解析token(包括创建token需要的信息)

              (4)刷新token和redis中存储的用户的有效期

              (5)将用户信息存入securityContextHolder中,使得过滤链上每个环节都能通过SecurityContextHolder拿到用户信息。放行。

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Autowired
    private JwtUtils jwtUtils;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //1.获取token
        String token = request.getHeader("authorization");
        //System.out.println(token);
        if (!StringUtils.hasText(token)){
            //放行
            filterChain.doFilter(request,response);
            return;
        }
        //2.解析token
        Integer userId;
        Integer userType;
        try {
             userId = jwtUtils.getUserIdFromToken(token);
             userType = jwtUtils.getUserTypeFromToken(token);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }

        //3.刷新token和redis的有效期
        String refreshToken = jwtUtils.refreshToken(token);
        response.setHeader("Access-Control-Expose-Headers","Authorization");
        response.addHeader("Authorization",refreshToken);

        if (SystemConstant.USER_TYPE_WUZHU==userType){

            SysUser sysUser = (SysUser) redisTemplate.opsForValue().get(RedisConstant.LOGIN_SYSTEM_USER_PRE + userId);
            if (Objects.isNull(sysUser)){
                throw new RuntimeException("用户未登录");
            }
            //刷新redis有效期
            redisTemplate.expire(RedisConstant.LOGIN_SYSTEM_USER_PRE + userId, RedisConstant.LOGIN_SYSTEM_USER_EXPIRE_TIME, TimeUnit.MINUTES);
            //3.将用户信息存入securityContextHolder中
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(sysUser, null, null);
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            filterChain.doFilter(request,response);
        }else {
            LiveUser liveUser = (LiveUser) redisTemplate.opsForValue().get(RedisConstant.LOGIN_LIVE_USER_PRE + userId);
            if (Objects.isNull(liveUser)){
                throw new RuntimeException("用户未登录");
            }
            redisTemplate.expire(RedisConstant.LOGIN_LIVE_USER_PRE+userId,RedisConstant.LOGIN_LIVE_USER_EXPIRE_TIME,TimeUnit.MINUTES);
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(liveUser, null, null);
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            filterChain.doFilter(request,response);
        }


    }
}

        3.登录接口

                返回result带有token令牌的map集合

        4.实现UserDetailsService

                基于数据库查询用户名对应的用户信息

       5.登录业务层

                (1)校验验证码

                        ①从redis中取出验证码和前端输入的验证码将进行比较,错误抛出异常

 private void validCaptcha(String captchaId, String captchaCode) {
        //1.从redis中获取验证码
        String captchaCode2 = (String) redisTemplate.opsForValue().get(RedisConstant.CAPTCHA_PRE + captchaId);
        if (!captchaCode.equalsIgnoreCase(captchaCode2)){
            throw new RuntimeException("验证码有误");
        }
    }

                (2)校验用户名密码返回认证信息

                        ①将用户名密码封装成usernamePasswordAuthenticationToken

                        ②通过authenticationManager调用认证方法,返回认证对象,认证对象为空抛出异常,反之返回认证对象

private Authentication validUsernameAndPassword(String username, Integer userType, String password) {
        username = username+":"+userType;
        //将用户名密码封装成usernamePasswordAuthenticationToken
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        //进行认证
        Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        if(Objects.isNull(authentication)){
            throw new RuntimeException("用户名或密码错误");
        }
        return authentication;

    }

                 (3)将用户信息存入redis,并响应token数据

                         ①用户信息在认证对象的主体中

                         ②创建token返回result带有token令牌的map集合

 private Result responseToken(Integer userType, Authentication authentication) {
        int userId;
        String username;
        //用户类型为物主
        if (userType== SystemConstant.USER_TYPE_WUZHU){
            SysUser sysUser = (SysUser) authentication.getPrincipal();
            userId = sysUser.getUserId();
            username = sysUser.getUsername();
            //将物主信息存到redis中
            redisTemplate.opsForValue().set(RedisConstant.LOGIN_SYSTEM_USER_PRE + userId, sysUser, RedisConstant.LOGIN_SYSTEM_USER_EXPIRE_TIME, TimeUnit.MINUTES);

        }else {
            LiveUser liveUser = (LiveUser) authentication.getPrincipal();
            userId = liveUser.getUserId();
            username = liveUser.getUsername();
            //将业主信息存入redis
            redisTemplate.opsForValue().set(RedisConstant.LOGIN_LIVE_USER_PRE + userId, liveUser, RedisConstant.LOGIN_LIVE_USER_EXPIRE_TIME, TimeUnit.MINUTES);
        }
        //创建token
        String token = jwtUtils.generateToken(userId, username, userType);
        HashMap<String, String> map = new HashMap<>();
        map.put("token",token);
        return Result.success(map);
    }

二.前端

        (1)验证码获取,将返回结果绑定到img标签的src属性中,带有前缀会自动解压

        (2)login()方法,将响应结果中的token存储到session storage中

        (3)配置http.js

                ①请求之前的拦截器,从session storage中获取token添加到请求头中(key为后端jwt过滤器中获取token的key)

//请求发送之前的拦截器
axios.interceptors.request.use(
  config => {
    let token = sessionStorage.getItem("authorization")
    //如果token存在,把token添加到请求的头部
    if (token) {
      config.headers['authorization'] = token
    }
    return config
  },
  error => {
    console.log(error)
    return Promise.reject(error)
  }
)

                ②请求返回之后的处理,从响应头中获取token并更新session storage中的token(前端刷新token)


//请求返回之后的处理
axios.interceptors.response.use(

  response => {
    if(response.headers.authorization){
      sessionStorage.setItem("authorization",response.headers.authorization)
  }
    const res = response.data

    if (res.code !== 200) {
      Message({
        message: res.msg || '服务器出错',
        type: 'error',
        duration: 5 * 1000
      })
      return Promise.reject(new Error(res.msg || '服务器出错'))
    } else {
      return res
    }
  },
  error => {
    console.log('err' + error) 
    Message({
      message: error.msg || '服务器出错!',
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

三.密码加密传输

3.1 节 加密的几种方式

在Java开发的过程中,很多场景下都需要加密解密,比如对敏感数据的加密,对配置文件信息的加密,通信数据的加密等等。

加密分为三类:

  1. 摘要加密(digest)

  2. 对称加密(symmetric)

  3. 非对称加密(asymmetric)

 3.1.1 摘要加密(digest)

说明:数字摘要是将任意长度的消息变成固定长度的短消息,它类似于一个自变量是消息的函数,也就是Hash函数。数字摘要就是采用单向Hash函数将需要加密的明文“摘要”成一串固定长度(128位)的密文这一串密文又称为数字指纹,它有固定的长度,而且不同的明文摘要成密文,其结果总是不同的,而同样的明文其摘要必定一致。 常见的有:

  1. MD5

  2. SHA

  3. 16进制编码

  4. Base64编码

 3.1.2 对称加密算法(symmetric)

 

对称加密算法是应用较早的加密算法,技术成熟。在对称加密算法中,数据发信方将明文(原始数据)和加密密钥(mi yao)一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。常见的有:

  1. DES

  2. AES(新)

3.1.3 非对称加密 (asymmetric) 

 非对称加密算法:又称为公开密钥加密算法,需要两个密钥,一个为公开密钥(PublicKey)即公钥,一个为私有密钥(PrivateKey)即私钥。两者需要配对使用。用其中一者加密,则必须用另一者解密。

常见的有:

  1. RSA 算法

  2. 数字签名

3.2 节 常见加密工具类使用

Hutool-all包含Hutool-crypto模块,hutool-crypto中包含创建的算法工具类,具体如下:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值