SpringBoot整合SpringSecurity实现用户登录(二)用户登录查库及权限判定

SpringSecurity整合MybatisPlus实现用户登录查库以及权限信息获取

登录Demo完整代码仓库地址:https://gitee.com/strivezhangp/java-study-log.git(分支为logindemo)

1 整体概述

​ 接上篇博客,本博客记录了SpringBoot项目结合SpringSecurity整合MybatisPlus3实现用户登录的查库认证以及用户权限的获取功能。

  • 整合MybatisPlus3
  • 结合MybatisX插件实现代码生
  • 用户权限的存储–将用户信息、权限信息存储到UsernamePasswordAuthenticationToken中

2 项目引入MybatisPlus相关依赖

2.1 Maven依赖引入

 <!-- 整合mybatis plus https://baomidou.com/-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

2.2 .yml配置文件汇总配置数据源信息

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/数据库名?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC
    username: ****
    password: ****
        
# mybatisPlus中mapper的xml文件对应位置,classpath指向的是resources文件夹
mybatis-plus:
  mapper-locations: classpath*:/mapper/**Mapper.xml

2.3 MybatisPlus配置类

@Configuration
@MapperScan("com.strive.logindemo.mapper") // mapper文件的扫描路径
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); // 防止全表更新
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 载入分页插件
        return interceptor;
    }
    @Bean
    public ConfigurationCustomizer configurationCustomizer(){
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
}

2.4 结合MybatisX插件自动生成代码

​ 基本的实现步骤:

  1. IEDA数据库工具中导入数据源
  2. 选择相应的数据表实现代码自动生成
  3. 代码生成的一些配置–点击完成后自动生成(service、mapper、实体类等)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    ​ 到此为止,数据库相关文件自动生成,可以进行相关的测试以及使用。
    在这里插入图片描述

3 实现用户登录的认证

3.1 自定义Jwt过滤器

​ 在此过滤器中,判断用户是否登录(登录后用户请求头拥有JWT令牌),然后根据令牌信息,查库后提取用户详细信息(用户名、密码、权限等),将其存储到会话中,方便后续该用户发起的请求的鉴权操作等。

​ 首先在JwtUtil工具类中追加以下方法:

/**
 * 解析JWT字符串
 *
 * @param jwt
 * @return
 */
public Claims getClaimByToken(String jwt) {
    try {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(jwt)
                .getBody();
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

/**
 * 判断JWT令牌是否过期
 *
 * @param claims
 * @return
 */
public boolean isTokenExpired(Claims claims) {
    return claims.getExpiration().before(new Date());
}

​ 然后自定义Jwt过滤器,在用户登录成功后,首先进入Jwt过滤器,对用户权限信息进行进一步的解析和获取。

public class JwtFilter extends BasicAuthenticationFilter {
    @Autowired
    JwtUtil jwtUtil;
    /**
     * 继承构造方法 使用 AuthenticationManager 来处理认证请求
     */
    public JwtFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    /**
     * 重写方法 实现自定义的用户请求处理逻辑
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String jwt = request.getHeader(jwtUtil.getHeader());

        // 判断是否存在JWT
        if (StrUtil.isBlankOrUndefined(jwt)) {
            chain.doFilter(request, response);
            return;
        }
        Claims claims = jwtUtil.getClaimByToken(jwt);
        if (claims == null) {
            throw new JwtException("token 异常");
        }
        // 判断是否过期
        if (jwtUtil.isTokenExpired(claims)) {
            throw new JwtException("token 已过期");
        }
        // 合法情况 生成token 存储在令牌中 包含 用户信息、用户权限信息
        String username = claims.getSubject();
        // 自定义一个用户权限数组
        String userAuth = "sys:user:list,sys:resident:list";
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
                username, // 返回用户名
                null, // 返回用户密码
                AuthorityUtils.commaSeparatedStringToAuthorityList(userAuth) // 返回用户权限信息
        );
        SecurityContextHolder.getContext().setAuthentication(token);
        System.out.println("jwt:" + jwt);
        System.out.println("token:" + token);
        chain.doFilter(request, response);
    }
}

3.2 将自定义过滤器引入Security配置中

// 引入自定义的过滤器
@Bean
JwtFilter jwtFilter() throws Exception {
    JwtFilter jwtFilter = new JwtFilter(authenticationManager());
    return jwtFilter;
}

// 相关配置项
.and()
.addFilter(jwtFilter()); // jwt过滤器判断用户请求是否存在JWT令牌

4 自定义 UserDetails类

​ 根据源码:package org.springframework.security.core.userdetails自定义一个UserAccount类,实现用户信息和权限信息的返回。

public class DefinedUser implements UserDetails {

    /**
     * 自定义一些变量
     */
    private Long userId;
    
    /** 此处复制源类的其他属性和构造方法 */
   
    /**
     * 重写一些方法(基本全部拿过来重写默认的User)
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities; // 返回权限
    }

    @Override
    public String getPassword() {
        return this.password; // 返回密码
    }

    @Override
    public String getUsername() {
        return this.username; // 返回用户名
    }

    @Override
    public boolean isAccountNonExpired() {
        return this.accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return this.accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return this.credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }
}

5 结合mybatisPlus实现用户登录查库操作

5.1 定义用户登录查库的Service

​ 在用户的登录提交后,跳转到Service层实现用户的查库操作。说明:在本类中未实现用户权限信息获取的查库操作,而是直接模拟了提供了一个用户权限信息。

@Service
public class UserDetailService implements UserDetailsService {
    @Autowired
    SysUserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("用户查库");
        SysUser user = userService.getByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户名或密码错误");
        }
        return new DefinedUser(user.getId(), user.getUsername(), user.getPassword(), getUserAuthority(user.getId()));
    }

    /**
     * 获取用户权限信息
     */
    public List<GrantedAuthority> getUserAuthority(Long id) {
        /**
         * 进行查库操作 获取用户权限信息
         */
        // 模拟一个用户权限信息
        String authority = "sys:user:list,sys:resident:list";
        return AuthorityUtils.commaSeparatedStringToAuthorityList(authority);
    }
}

5.2 Security配置

​ 添加查库操作后,用户登录表单提交,要实现用户查库操作,验证用户名和密码,配置文件中需要引入加密方式以及用户验证查库的Service。

// 类开始引入密码加密方式
// 密码加密形式
@Bean
BCryptPasswordEncoder bCryptPasswordEncoder() {
    return new BCryptPasswordEncoder();
}

// 类中实现方法
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
    authenticationManagerBuilder.userDetailsService(userDetailService);
}

6 Security配置添加权限验证

​ 在Security配置类中,添加以下注解,开启全局权限验证。

/**
 * Security配置类
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启全局安全检查(权限判断)
public class SecurityConfig extends WebSecurityConfigurerAdapter...

6.1 添加用户没有权限的异常处理器

/**
 * 用户没有接口 访问权限 处理器
 */
@Component
public class UserNotAuthorityHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");

        // 权限不足 返回 403
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();

        outputStream.write(JSONUtil.toJsonStr(Result.failure(e.getMessage())).getBytes("UTF-8"));
        outputStream.flush();
        outputStream.close();
    }
}

6.2 Security配置中进行相关配置

// 异常处理
.and()
.exceptionHandling()
.authenticationEntryPoint(userNotAuthenticated) // 用户未认证异常处理
.accessDeniedHandler(userNotAuthorityHandler) // 权限不组处理器

结果测试

// 模拟一个用户权限信息
String authority = "sys:user:list,sys:resident:list";

// 测试Controller中 权限的鉴定方式如下
@PreAuthorize("hasAuthority('sys:user:list')") // 菜单权限鉴定
@GetMapping("/test/list")
public Result testPermMenu(){
    return Result.success("用户有该菜单或按钮的访问权限");
}

@PreAuthorize("hasRole('admin')") // 角色权限鉴定
@GetMapping("/test/admin")
public Result testPermRole(){
    return Result.success("用户角色有访问权限");
}

在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值