【SpringSecurity】三更草堂项目案例分析2 - 认证主体业务编写

2023-09-17 12:48:14

认证主体业务

配置 mybatisplus

为 User 创建 mapper

代码清单:/mapper/UserMapper.java

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

在入口类中对该 mapper 执行扫描

@SpringBootApplication
@MapperScan("com.zhiller.sangengsecurity.mapper")
public class SanGengSecurityApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(SanGengSecurityApplication.class, args);

    }
}

UserDetailsServiceImpl

该实现类实现了 UserDetailsService ,通过条件查询从 mysql 中找到对应用户
如果用户不存在,抛出异常
如果用户存在,返回一个 UserDetails 对象

代码清单:/service/UserDetailsServiceImpl.java

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名查询用户信息
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUserName,username);
        User user = userMapper.selectOne(wrapper);
        //如果查询不到数据就通过抛出异常来给出提示
        if(Objects.isNull(user)){
            throw new RuntimeException("用户名或密码错误");
        }
        //TODO 根据用户查询权限信息 添加到LoginUser中

        //封装成UserDetails对象返回
        return new LoginUser(user);
    }
}

LoginUser

该实体类继承了 UserDetails

@AllArgsConstructor 注解可以自动生成带参数的构造函数,因为我们已经在该类中设置了私有变量 User,所以生成的构造函数就会自带形参 user,然后对应 UserDetailsServiceImpl 中的末尾返回的就是一个 UserDetials 对象

默认的 LoginUser 对象仅需要两个参数:userid 和 password

代码清单:/domain/LoginUser.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
    private User user;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }


    // 下面的这四个玩意必须设置成true,否则不给你验证!!!
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }
}

LoginService

设置登录服务接口

代码清单:/service/LoginService.java

public interface LoginService {
    ResponseResult login(User user);

    ResponseResult logout();
}

LoginServiceImpl

实现登录与登出服务

由于内容过多,具体代码功能查看下方注释,十分详细

代码清单:/service/LoginServiceImpl.java

@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisCache redisCache;

    @Override
    public ResponseResult login(User user) {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());

        // AuthenticationManager对象,用于处理认证操作
        // authenticate()方法会触发Spring Security进行认证,返回一个Authentication对象authenticate,表示认证成功
        // 如果认证失败,即authenticate为null,则抛出RuntimeException异常
        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);
        //authenticate存入redis
        redisCache.setCacheObject("login:"+userId,loginUser);
        //把token响应给前端
        HashMap<String,String> map = new HashMap<>();
        map.put("token",jwt);
        return new ResponseResult(200,"登陆成功",map);
    }

    @Override
    public ResponseResult logout() {
        // 通过SecurityContextHolder获取当前登录用户的认证信息Authentication
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        // 认证信息中的主体对象转换为LoginUser对象
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        Long userid = loginUser.getUser().getId();
        // 删除redis数据库中的对应对象
        redisCache.deleteObject("login:"+userid);
        return new ResponseResult(200,"退出成功");
    }
}

LoginController

代码清单:/controller/LoginController.java

@RestController
public class LoginController {

    @Autowired
    private LoginService loginService;

    @PostMapping("/user/login")
    public ResponseResult login(@RequestBody User user) {
        return loginService.login(user);
    }

    @RequestMapping("/user/logout")
    public ResponseResult logout() {
        return loginService.logout();
    }
}
JWT 过滤器

配置 JWT 过滤器,实现用户 JWT 校验

代码清单:/filter/JwtAuthenticationTokenFilter.java

// 继承自OncePerRequestFilter,它是Spring提供的一个过滤器基类,确保每个请求只被过滤一次
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;

    // 实现了具体的过滤逻辑
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            //放行
            filterChain.doFilter(request, response);
            return;
        }

        // 解析token
        // 因为JWT中的Subject存储的就是userid,JWT解析后可以取出来放入redis进行比对
        String userid;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }

        //从redis中获取用户信息
        String redisKey = "login:" + userid;
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if(Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }

        // 存入SecurityContextHolder,便于后续logout方法直接从这里面取出当前登录的用户信息
        //TODO 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser,null,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        //放行
        filterChain.doFilter(request, response);
    }
}

SecurityConfig

配置 security 基本属性

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // security自带的BCryptPasswordEncoder来对用户密码进行加密
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 自动装配JWT过滤器
    @Autowired
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        //把token校验过滤器添加到过滤器链中
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

更多推荐

卡尔曼滤波(Kalman Filter)C#测试

一、操作过程刚学了一下卡尔曼滤波,具体原理还没细看,大致过程如下分为两步,第一步Predict,以下两个公式第二步Correct,以下三个公式公式看起来很复杂,其中是我们要处理的数据,是滤波之后的值,其他一些有些是需要给定的,有些是中间值。从t=1时刻开始,通过第一步,计算得到的值,给到第二步Correct里,第二步的

java入坑之Jsoup(待补充)

一、快速入门1.1配置<dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.16.1</version></dependency>1.2解析xmlJsoup:jsoup是一款Java的HTML解析器,可直接解析某

C++宏的用法

​1.命名空间:这样可以把不同功能段的代码进一步封装起来#defineBEGINS(x)namesapcex{#defineENDS(x)}BEGINS(TEST1)voidfunc(){printf("helloworld");}intmain(){func();return0;}ENDS(TEST1)BEGINS(

19.组合模式(Composite)

意图:将对象组成树状结构以表示“部分-整体”的层次结构,使得Client对单个对象和组合对象的使用具有一致性。上下文:在树型结构的问题中,Client必须以不同的方式处理单个对象和组合对象。能否提供一种封装,统一简单元素和复杂元素的概念,让对象容器自己来实现自身的复杂结构,让Client可以像处理简单元素一样来处理复杂

leetcode 332. Reconstruct Itinerary(重构行程)

有一些票tickets,tickets[i]=[from,to],每个出发到达城市名字都是3个大写英文字母,同一个出发城市时,优先去字母顺序较小的到达城市。必须先从“JFK”出发。每个ticket必须用且只用一次,所有ticket一定会形成至少一个有效的行程(出发至到达的一路上遍历所有城市)按顺序记录行程上的城市并返回

应用商店优化之关键词优化指南2

在寻找良好关键词的方法中,分析竞争对手是寻找关键词的最佳且最可靠的方法。查找排名最高的关键词或者是获得最多下载量的关键词,这样就可以准确的瞄准那些带来最大价值的关键词,从而带来更好的优化效果。1、选择正确的关键词。搜索分数用来衡量关键词流行度的分数。该分数是一个从1到100的数字,分数越高,对该关键词进行的搜索就越多。

MySQL索引,Explain,事务,锁与MVCC

MySQL的索引为什么不能为二叉树假如为二叉树,索引值插入顺序为1,2,3,4,5,6,那么形成的索引结构如图:搜索效率并不高。此时可以优化为红黑树(二叉平衡树),如图:但是红黑树也有问题,就是树的高度,如果数据过多,红黑树过高也会影响效率。为了控制高度,可以给每一个节点分配大一点的空间,例如上面的0002节点可以存储

css自学框架之图片懒加载

首先解释一下什么叫图片懒加载。图片懒加载是一种在页面加载时,延迟加载图片资源的技术,也就是说图片资源在需要的时候才会加载,就是在屏幕显示范围内加载图片,屏幕显示范围外图片不加载。一、关键函数用到的关键函数:globalThis.IntersectionObserver。varobserver=newIntersecti

亚马逊应该怎么快速提升排名,获取review?

跨境电商做久了,卖家都会陷入一个困境,到底是该坚持慢慢做好,还是要测评?现在跨境电商平台人人都在刷,不刷单想成功真的很难,不是没可能,但是选品要非常好,而且你的listing也要做好,推广要求又高你看那些大卖的评论长篇大论,图片视频样样都有,说是国外人写的,我估计都没人信,只是你还不会测评而已,会的话你就不会惊讶了但老

MyBatis 分页插件 PageHelper

文章目录前言PageHelper应用实现原理剖析应用场景分析前言分页插件PageHelper是我们使用Mybatis到的比较多的插件应用,适用于任何复杂的单表、多表分页查询操作。本文介绍PageHelper的使用及原理。PageHelper应用添加依赖<dependency><groupId>com.github.pa

VMware Workstation Pro各版本下载安装教程

VMwareWorkstationPro下载打开浏览器,输入VMwareWorkstationPro找到VMwareWorkstationPro官网并点击进入,官网地址:https://www.vmware.com/cn/products/workstation-pro.html进入官网首页后可以下载最新版本的VMwa

热文推荐