首页 > 其他分享 >Spring Security - authentication

Spring Security - authentication

时间:2023-08-16 19:31:35浏览次数:30  
标签:return Spring token authentication user Override new Security public

前言 : SpringSecurity是Spring家族中的安全框架,主要功能有两个认证(authentication)授权(authorization)

认证

认证核心过滤器链流程图

image.png

  1. 前端发送请求
  2. AuthenticationFilter拦截请求 调用UsernamePassowordAuthenticationToken
  3. UsernamePassowordAuthenticationToken 将前端发送的表单中username和password封装成对象
  4. AuthenticationMananger调用认证器,认证UsernamePassowordAuthenticationToken 对象
  5. 认证UsernamePassowordAuthenticationToken 对象的时候,默认情况AuthenticationProvider从内存中读取用户信息。我们可以通过实现UserDetailService接口来实现自定义的获取用户对象的逻辑。
  6. 当UsernamePassowordAuthenticationToken 对象 与UserDetail对象比较之后,两个相等AuthenticationMananger会生产一个Authentication对象并将UserDetail对象封装成属性通过验证。不相等直接报错没有通过验证
  7. 所有通过验证的Authentication会被保存到SecurityContextHolder中

如果要自定义获取到存放的用户信息,需要提供一个密码校验器来比较密码

认证代码

 <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.6.6</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

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

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

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-jwt</artifactId>
            <version>5.8.19</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>


        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.26</version>
        </dependency>

    </dependencies>

当我们引入了spring-boot-starter-security场景的时候,SpringSecurity就已经生效了。当我们没有认证去访问Controller层时Spring Security会帮我们跳转到登录页面。

注意: 这时候我们的用户名和密码都是固定的,我们不希望这样所以我们要自定义Spring Securiyt的执行链

UserDetailService

用来定义获取数据库的用户信息,这样我们的用户不是固定了

public UserDetialServiceImpl implements UserDetailService {

	@Autowired
	private UserMapper userMapper;

	 @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1. 根据前端传输过来的Username从数据库找到对应的用户信息
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername, username);
        User user = userMapper.selectOne(queryWrapper);

        // 判断是否存在当前用户
        if (Objects.isNull(user)) {
            throw new RuntimeException("用户名或密码错误");
        };

	// 获取当前用户的角色
        List<String> roleList = userMapper.getRole(user.getId());
        log.info(roleList.toString());
        user.setRoles(roleList);
	// 将用户信息添加到缓存中
        redisTemplate.boundValueOps(String.valueOf(user.getId())).set(user);
        return new LoginUser(user);
    }

}

LoginUser

因为DetailUserService需要返回UserDetail,我们就创建一个类来继承

public class LoginUser implements UserDetails {
    private User user;
    // 获取当前用户的权限
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // TODO 进行处理
      log.info(user.getRoles().toString());
        return user.getRoles().stream().map(role -> new SimpleGrantedAuthority(role)).collect(Collectors.toList());
    }

    // 获取当前用户的密码
    @Override
    public String getPassword() {
        return user.getPassword();
    }

    // 获取当前用户的用户名
    @Override
    public String getUsername() {
        return user.getUsername();
    }

    // 判断用户是否没到期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // 判断用户是否被锁
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // 密码是否过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // 是否可用
    @Override
    public boolean isEnabled() {
        return true;
    }
}

User是映射数据的实体类对象

BCryptPasswordEncoder

因为我们自定义了获取用户信息,所以我们要提供一个密码校验器

SecurityConfig 配置Spring Security的配置类

// Spring Security配置类需要继承WebSecurityConfigurerAdapter 
@Component
public class SecurityConfig exends WebSecurityConfigurerAdapter {

   // 设置密码校验器
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder () {
        return new BCryptPasswordEncoder();
    }
}

注意在保存密码的时候需要将密码加密,如果没有加密就在密码前面加上 {noop}

数据库脚本

create table tb_security_user (
	id bigint primary key auto_increment,
	username varchar(30) not null comment '用户名',
	password varchar(30) not null comment '密码',
	is_deleted int not null default 0 comment '是否被删除 0 没有 1 有',
	create_time datetime default CURRENT_TIMESTAMP(0) comment '创建时间',
    update_time datetime default  CURRENT_TIMESTAMP(0) comment '修改时间'
)

Contoller

/login路径是SpringSecurity默认的登录路径(可以修改), 当登录成功的时候返回一个有UserId和Username生产的Token并返回给前端


@RestController
@RequestMapping("/login")
public class LoginController {

    @Autowired
    private LoginService loginService;

    @PostMapping
    public Result login (@RequestBody User user) {
        String token = loginService.login(user);
        return Result.succeed().data("token", token);
    }
}

Service

public interface LoginService {
    String login(User user);
}

ServiceImpl

@Service
public class LoginServiceImpl implement LoginService {
	 @Autowired
    private AuthenticationManager authenticationManager;

	@Override
    public String login(User user) {
	// 创建UsernamePasswordAuthenticationToken 将前端传递过来的表单值封装
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
	// 在Spring Security的配置文件中配置上Authentication 对象
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

	// 判断是否登录成功
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("登录失败");
        }
	// 取出登录成功的LoginUser对象
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();

	// 生成token
        Map<String, Object> payload = new HashMap<>();
        payload.put("id", loginUser.getUser().getId());
        payload.put("username", loginUser.getUsername());
        String token = JWTUtil.createToken(payload, SystemConst.SALT.getBytes(StandardCharsets.UTF_8));
        return token;
    }
}

SecurityConfig

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

自定义过滤器

因为我们判断一个用户是否已经登录是通过请求头是否携带token所以我们要自定义一个过滤器来优化判断是否登录 TokenFilter

@Component
public class TokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
	// 请求头没有携带token的情况,表示当前用户没有登录
        String token = request.getHeader("token");
        if (!StringUtils.hasLength(token)) {
            // 没有携带token的情况
            // 放行
            filterChain.doFilter(request, response);
            return;
        }
        
	// 表示当前用户登录过,但不知道是否登出,我们判断缓存中是否有当前对象
        // 解析token
        JWT jwt = JWTUtil.parseToken(token);
        Long id = ((NumberWithFormat) jwt.getPayload("id")).longValue();
        String username = (String) jwt.getPayload("username");

        // 根据id在缓存中查找是否存在
       User user = (User) redisTemplate.opsForValue().get(String.valueOf(id));
	
	// 是否登出
        if (Objects.isNull(user)) {
            throw new RuntimeException("登录失败");
        }

        LoginUser loginUser = new LoginUser(user);
        // authorities 权限  DetailUser.get
	
	// 用户认证成把用户信息存放到SecurityContex中
        List list = new ArrayList();
        list.add(new SimpleGrantedAuthority("test"));
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null,list);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request, response);
    }
}

配置Spring Security


	@Override
    protected void configure(HttpSecurity http) throws Exception {
        // 基本配置
        http
                .csrf().disable()
                // 不用生产session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // 处理认证请求
                .authorizeRequests()
                .antMatchers("/login").anonymous()
                .anyRequest().authenticated();

        // 添加过滤器
        http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

logout

只需要根据 userId把缓存移除就可以了 Contoller层的路径为 /logout

标签:return,Spring,token,authentication,user,Override,new,Security,public
From: https://blog.51cto.com/u_15497049/7112761

相关文章

  • spring注入bean错误-Bean named 'abc' is expected to be of type 'AAA' but was actu
    先看如下两个注入到spring容器中的bean,一个是UserNewManager,一个是UserManager。@ServicepublicclassUserNewManager{publicvoiddoSomething(){}}@ServicepublicclassUserManager{...}再看下面的testcase,利用@Resource注解来注入bean。@......
  • springmvc 使用 DeferredResult
    背景:需求:可以实时获取啄木鸟伍迪的访问数据、排名、积分等数据,可以实时现在在网站后台页面的大屏上;解决方案:可以使用异步请求,springmvc默认的请求都是同步的,也就是请求过去,必须得有处理完成,否则就回阻塞;异步请求是当发起一个请求,可以暂时没有响应,请求回被挂起不阻塞;请求过去,就......
  • 深入解析Spring Boot自动配置原理
    大家好,我是你的技术达人小助手,在今天的技术博客中,我将带你深入解析SpringBoot自动配置的原理,揭示其神秘面纱,让你能更好地理解和利用这一强大的功能。准备好了吗?让我们一起开始这次关于SpringBoot自动配置的探索之旅吧!背景知识SpringBoot作为一款优秀的Java开发框架,以其方便的配......
  • 深入探索Spring Cloud微服务架构
    嗨,亲爱的读者朋友们!今天,我将带你深入探索SpringCloud微服务架构的精髓,解析其在现代分布式系统中的重要地位。作为一个技术达人,我将从基本概念到核心组件,带你领略SpringCloud的魔力,让你在微服务世界中游刃有余。微服务架构的背景在现代应用开发中,微服务架构越来越受欢迎。它通过将......
  • 解密Spring Framework的核心原理与魔法
    嗨,亲爱的读者朋友们!今天,我将带你解密SpringFramework的核心原理与魔法,帮助你深入理解这个强大的Java开发框架。作为一个技术达人,我将为你揭开SpringFramework的神秘面纱,让你在开发中游刃有余。SpringFramework简介SpringFramework是一个全面的、模块化的Java开发框架,被广泛用于......
  • 构建高性能微服务架构:深入探索Spring Cloud与Spring Boot
    嗨,亲爱的读者朋友们!今天,我将带你深入探索如何通过SpringCloud和SpringBoot构建高性能微服务架构,为你揭示这个充满活力和机遇的技术领域。作为一个技术达人,我将从基本概念到关键组件,为你展示如何在现代应用开发中构建强大的微服务架构。微服务架构的兴起微服务架构正以惊人的速度......
  • 构建高性能微服务架构:深入探索Spring Cloud与Spring Boot
    嗨,亲爱的读者朋友们!今天,我将带你深入探索如何通过SpringCloud与SpringBoot构建高性能微服务架构,为你揭示这个充满活力与机遇的技术领域。作为一个技术达人,我将从基础概念到关键组件,为你呈现如何在现代应用开发中构建强大的微服务架构。微服务架构的兴起微服务架构正以惊人的速度......
  • springmvc 开启异步请求报错 Java code using the Servlet API or by adding "true"
    报错内容:java.lang.IllegalStateException:Asyncsupportmustbeenabledonaservletandforallfiltersinvolvedinasyncrequestprocessing.ThisisdoneinJavacodeusingtheServletAPIorbyadding"true"toservletandfilterdeclarationsin......
  • spring-boot静态资源目录配置
    spring-boot静态资源目录配置spring-boot静态资源默认为/src/main/resources下的/static目录,可以通过application.properties的server.servlet.context-path属性配置如:server.servlet.context-path=/public......
  • Apche Kafka + Spring的消息监听容器
    (目录)一、消息的接收消息的接收:可以通过配置MessageListenerContainer并提供消息侦听器或使用@KafkaListener注释来接收消息。本章我们主要说明通过配置MessageListenerContainer并提供消息侦听器的方式接收消息。1.1、消息监听器当使用消息监听容器时,就必须提供一个监听器......