文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring Security进行登录认证和授权

2023-09-02 07:21

关注

一、Spring Security内部认证流程

  1. 用户首次登录提交用户名和密码后spring security 的UsernamePasswordAuthenticationFilter把用户名密码封装Authentication对象

  2. 然后内部调用ProvideManagerauthenticate方法进行认证,然后ProvideManager进一步通过内部调用DaoAuthencationPrioviderauthenticate方法进行认证

  3. DaoAuthencationPriovider通过调用UserDetailsService的实现类InMemoryDetialManagerloadUserByUsername方法查询用户,并把查询到的用户信息封装成一个UserDetails对象。注意InMemoryDetialManage是从内存中查询用户信息的,所以我们要定制化实现UserDetailsService接口来达到从数据库查询用户信息。

  4. 通过PasswordEncode对比返回的UserDetails对象的密码和Authentication对象的密码是否一致,如果密码正确就把UserDetails中的权限信息设置到Authentication对象中

  5. 如果成功返回Authentication对象就使用SecurityContextHolder.getContext().setAuthentication方法存储改对象,然后其他过滤器就会通过SecurityContextHolder来获取当前用户信息。

在这里插入图片描述

二、自定义认证流程

我们要定制化实现UserDetailsService接口来达到从数据库查询用户信息,所以重新写一个UserDetailsServiceImpl实现类。然后认证通过使用用户id生成一个jwt(也就是token),然后用userId 作为key,用户信息作为value存入redis中。用户第一次登陆认证流程如下图。

在这里插入图片描述
导入依赖

  <!--SpringSecurity启动器-->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-security</artifactId>        </dependency>        <!--redis依赖-->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-data-redis</artifactId>        </dependency>        <!--fastjson依赖-->        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>fastjson</artifactId>        </dependency>        <!--jwt依赖-->        <dependency>            <groupId>io.jsonwebtoken</groupId>            <artifactId>jjwt</artifactId>        </dependency>

UserDetailsServiceImpl代码

@Servicepublic class UserDetailServiceImpl implements UserDetailsService {    @Resource    private UserMapper userMapper;    @Override    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        //根据用户名查询用户信息        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();        queryWrapper.eq(User::getUserName,username);        User user = userMapper.selectOne(queryWrapper);        //判断是否查到用户  如果没查到抛出异常        if(Objects.isNull(user)){            throw new RuntimeException("用户名不存在");        }        //返回用户信息        // TODO 查询权限信息封装        return new LoginUser(user);    }}

BlogLoginServiceImpl 登陆认证代码

@Servicepublic class BlogLoginServiceImpl implements BlogLoginService {    @Autowired    private AuthenticationManager authenticationManager;    @Autowired    private RedisCache redisCache;    @Override    public ResponseResult login(User user) {        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());        Authentication authenticate = authenticationManager.authenticate(authenticationToken);        //判断是否认证通过       if(Objects.isNull(authenticate)){           throw new RuntimeException("用户名或密码错误");       }        //获取userid 生成token        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();        String userId = loginUser.getUser().getId().toString();        String jwt = JwtUtil.createJWT(userId);        //把用户信息存入redis        redisCache.setCacheObject("bloglogin:"+userId,loginUser);        //把token和userinfo封装 返回        UserInfoVo userInfoVo = BeanCopyUtil.copyBean(loginUser.getUser(), UserInfoVo.class);        BlogUserLoginVo blogUserLoginVo = new BlogUserLoginVo(jwt, userInfoVo);        //把User转换成UserInfoVo        return ResponseResult.okResult(blogUserLoginVo);    }}

SecurityConfig配置类

@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {    @Bean    public PasswordEncoder passwordEncoder(){        return new BCryptPasswordEncoder();    }    @Override    protected void configure(HttpSecurity http) throws Exception {        http                //关闭csrf                .csrf().disable()                //不通过Session获取SecurityContext                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)                .and()                .authorizeRequests()                // 对于登录接口 允许匿名访问                .antMatchers("/login").anonymous()                // 除上面外的所有请求全部不需要认证即可访问                .anyRequest().permitAll();        http.logout().disable();        //允许跨域        http.cors();    }    @Override    @Bean    public AuthenticationManager authenticationManagerBean() throws Exception {        return super.authenticationManagerBean();    }}

JwtUtil工具类

public class JwtUtil {    //有效期为    public static final Long JWT_TTL = 24*60 * 60 *1000L;// 60 * 60 *1000  一个小时    //设置秘钥明文    public static final String JWT_KEY = "hhh";    public static String getUUID(){        String token = UUID.randomUUID().toString().replaceAll("-", "");        return token;    }            public static String createJWT(String subject) {        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间        return builder.compact();    }        public static String createJWT(String subject, Long ttlMillis) {        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间        return builder.compact();    }    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;        SecretKey secretKey = generalKey();        long nowMillis = System.currentTimeMillis();        Date now = new Date(nowMillis);        if(ttlMillis==null){            ttlMillis=JwtUtil.JWT_TTL;        }        long expMillis = nowMillis + ttlMillis;        Date expDate = new Date(expMillis);        return Jwts.builder()                .setId(uuid)              //唯一的ID                .setSubject(subject)   // 主题  可以是JSON数据                .setIssuer("sg")     // 签发者                .setIssuedAt(now)      // 签发时间                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥                .setExpiration(expDate);    }        public static String createJWT(String id, String subject, Long ttlMillis) {        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间        return builder.compact();    }    public static void main(String[] args) throws Exception {        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";        Claims claims = parseJWT(token);        System.out.println(claims);    }        public static SecretKey generalKey() {        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");        return key;    }            public static Claims parseJWT(String jwt) throws Exception {        SecretKey secretKey = generalKey();        return Jwts.parser()                .setSigningKey(secretKey)                .parseClaimsJws(jwt)                .getBody();    }}

redis缓存配置类

@Configurationpublic class RedisConfig {    @Bean    @SuppressWarnings(value = { "unchecked", "rawtypes" })    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)    {        RedisTemplate<Object, Object> template = new RedisTemplate<>();        template.setConnectionFactory(connectionFactory);        FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);        // 使用StringRedisSerializer来序列化和反序列化redis的key值        template.setKeySerializer(new StringRedisSerializer());        template.setValueSerializer(serializer);        // Hash的key也采用StringRedisSerializer的序列化方式        template.setHashKeySerializer(new StringRedisSerializer());        template.setHashValueSerializer(serializer);        template.afterPropertiesSet();        return template;    }}

下次用户再次发请求时带token(相对于没有前后端分离时的session)来进行访问,如果没有携带则不让访问,如果有就经过一个jwt认证过滤器来获取token,解析token,最后获取userId,封装成Authentication对象存入SecurityContextHolderredis中以便其他过滤器或者资源来获取当前用户请求信息
在这里插入图片描述

最后感悟:springboot约定大于配置,许多接口都可以自定义,达到定制化功能,这也体现了springboot的强大,还有许多功能还没完成,授权功能和流程将在后续完成,敬请期待。

来源地址:https://blog.csdn.net/afjja/article/details/126428728

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯