文章详情

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

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring Boot Security + JWT Token 的简单应用

2024-11-30 03:54

关注

今日内容介绍,大约花费40分钟

图片

1.Spring Boot 注册和登录with JWT 身份验证流程

下图显示了我们如何实现用户注册、用户登录和授权流程的流程。

图片

如果客户端访问受保护的资源,则必须将合法的 JWT 添加到 HTTP 授权标头中。

Spring Boot中使用Spring Security

您可以通过下图概述我们的Spring Boot项目:

图片

Spring Security介绍:

注意:WebSecurityConfigurerAdapter 从 SpringBoot 2.7.0 开始被弃用)

2.项目准备

下图是Spring Boot项目的文件夹和文件结构:

图片

图片

2.1. 创建表

根据Sql创建表,表间关系如下:

图片

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for pe_permission
-- ----------------------------
DROP TABLE IF EXISTS `pe_permission`;
CREATE TABLE `pe_permission` (
                                 `id` varchar(40) NOT NULL COMMENT '主键',
                                 `name` varchar(255) DEFAULT NULL COMMENT '权限名称',
                                 `code` varchar(20) DEFAULT NULL,
                                 `description` text COMMENT '权限描述',
                                 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of pe_permission
-- ----------------------------
INSERT INTO `pe_permission` VALUES ('1', '添加用户', 'user-add', null);
INSERT INTO `pe_permission` VALUES ('2', '查询用户', 'user-find', null);
INSERT INTO `pe_permission` VALUES ('3', '更新用户', 'user-update', null);
INSERT INTO `pe_permission` VALUES ('4', '删除用户', 'user-delete', null);

-- ----------------------------
-- Table structure for pe_role
-- ----------------------------
DROP TABLE IF EXISTS `pe_role`;
CREATE TABLE `pe_role` (
                           `id` varchar(40) NOT NULL COMMENT '主键ID',
                           `name` varchar(40) DEFAULT NULL COMMENT '权限名称',
                           `description` varchar(255) DEFAULT NULL COMMENT '说明',
                           PRIMARY KEY (`id`),
                           UNIQUE KEY `UK_k3beff7qglfn58qsf2yvbg41i` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of pe_role
-- ----------------------------
INSERT INTO `pe_role` VALUES ('1', '系统管理员', '系统日常维护');
INSERT INTO `pe_role` VALUES ('2', '普通员工', '普通操作权限');

-- ----------------------------
-- Table structure for pe_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `pe_role_permission`;
CREATE TABLE `pe_role_permission` (
                                      `role_id` varchar(40) NOT NULL COMMENT '角色ID',
                                      `permission_id` varchar(40) NOT NULL COMMENT '权限ID',
                                      PRIMARY KEY (`role_id`,`permission_id`),
                                      KEY `FK74qx7rkbtq2wqms78gljv87a0` (`permission_id`),
                                      KEY `FKee9dk0vg99shvsytflym6egxd` (`role_id`),
                                      CONSTRAINT `fk-p-rid` FOREIGN KEY (`role_id`) REFERENCES `pe_role` (`id`),
                                      CONSTRAINT `fk-pid` FOREIGN KEY (`permission_id`) REFERENCES `pe_permission` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of pe_role_permission
-- ----------------------------
INSERT INTO `pe_role_permission` VALUES ('1', '1');
INSERT INTO `pe_role_permission` VALUES ('1', '2');
INSERT INTO `pe_role_permission` VALUES ('2', '2');
INSERT INTO `pe_role_permission` VALUES ('1', '3');
INSERT INTO `pe_role_permission` VALUES ('1', '4');

-- ----------------------------
-- Table structure for pe_user
-- ----------------------------
DROP TABLE IF EXISTS `pe_user`;
CREATE TABLE `pe_user` (
                           `id` varchar(40) NOT NULL COMMENT 'ID',
                           `username` varchar(255) NOT NULL COMMENT '用户名称',
                           `password` varchar(255) DEFAULT NULL COMMENT '密码',
                           PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of pe_user
-- ----------------------------
INSERT INTO `pe_user` VALUES ('1', 'zhangsan', '$2a$10$.fXoccJHJkb9KM1FYJd1Ve.2P0B0RgLvloBDPwGjRxcP2obt2NRkG');
INSERT INTO `pe_user` VALUES ('2', 'lisi', '$2a$10$.fXoccJHJkb9KM1FYJd1Ve.2P0B0RgLvloBDPwGjRxcP2obt2NRkG');
INSERT INTO `pe_user` VALUES ('3', 'wangwu', '$2a$10$.fXoccJHJkb9KM1FYJd1Ve.2P0B0RgLvloBDPwGjRxcP2obt2NRkG');

-- ----------------------------
-- Table structure for pe_user_role
-- ----------------------------
DROP TABLE IF EXISTS `pe_user_role`;
CREATE TABLE `pe_user_role` (
                                `role_id` varchar(40) NOT NULL COMMENT '角色ID',
                                `user_id` varchar(40) NOT NULL COMMENT '权限ID',
                                KEY `FK74qx7rkbtq2wqms78gljv87a1` (`role_id`),
                                KEY `FKee9dk0vg99shvsytflym6egx1` (`user_id`),
                                CONSTRAINT `fk-rid` FOREIGN KEY (`role_id`) REFERENCES `pe_role` (`id`),
                                CONSTRAINT `fk-uid` FOREIGN KEY (`user_id`) REFERENCES `pe_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of pe_user_role
-- ----------------------------
INSERT INTO `pe_user_role` VALUES ('1', '1');

2.2. 在pom.xml中添加依赖



    4.0.0

    
        spring-boot-starter-parent
        org.springframework.boot
        2.7.15
    

    com.zbbmeta
    spring-boot-backend-example
    1.0-SNAPSHOT

    
        8
        8
    


    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
        

        
            com.baomidou
            mybatis-plus-boot-starter
            3.5.3
        

        
            mysql
            mysql-connector-java
            8.0.30
        


        
            org.projectlombok
            lombok
        

        
            cn.hutool
            hutool-all
            5.8.20
        
        
        
            org.springframework.boot
            spring-boot-configuration-processor

        

        
            org.springframework.boot
            spring-boot-starter-security
        

        
            io.jsonwebtoken
            jjwt
            0.9.1
        


    

2.3. 使用MyBatisX插件生成代码

安装MyBatisX插件,这里就不过多介绍如何安装插件了,和其他插件安装相同 表生成代码步骤如下:

图片

图片

图片

2.4. 创建UserDetailsService实现类

Spring Security 将加载用户详细信息以执行身份验证和授权。所以它有 UserDetailsService 我们需要实现的接口。

在com.zbbmeta.service.impl包下创建UserDetailsService实现类UserDetailsServiceImpl

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userService.findByName(username);
        List roles = user.getRoles();
        List authorities = new ArrayList<>();
        for (Role role : roles) {
            List permissions = role.getPermissions();

            List collect = permissions.stream().map(x -> x.getCode()).distinct().collect(Collectors.toList());
            authorities.addAll(collect);
        }


        List collect1 = authorities.stream().distinct().collect(Collectors.toList());
        return   new org.springframework.security.core.userdetails.User(username, user.getPassword(), AuthorityUtils.createAuthorityList(collect1.toString()));

    }
}

2.5. 在Mapper类中添加查询用户和查询权限方法

public interface UserMapper extends BaseMapper {

    User findByUsername(String name);
}

在User实体类中添加字段roles

@TableField(exist = false)
    private List roles = new ArrayList<>();//用户与角色   多对多

图片

并且在resultMap添加一对多根据用户查询对应角色





    
            
            
            

        
        
            
            
            
        

    

    
        id,username,password
    

    
        select distinct pr.* from pe_user_role pur
                                      inner join pe_role pr on pur.role_id =pr.id
        where pur.user_id =#{id}
    
public interface PermissionMapper extends BaseMapper {
    List queryPermissionList(@Param("id") Long id);
}




    
            
            
            
            


    

    
        id,name,code,
        description
    

    

3. 配置 Spring Security

注意:不使用 WebSecurityConfigurerAdapter,因为WebSecurityConfigurerAdapter 从 Spring 2.7.0 中弃用

【步骤一】: 在application.yml 添加jwt配置

jwt:
  config:
    key: zbbmeta
    ttl: 3600

【步骤一】: 创建JwtUtil工具类

在com.zbbmeta.util包下创建JwtUtil类

package com.zbbmeta.util;

import io.jsonwebtoken.*;
import lombok.Data;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Map;


@Component
@ConfigurationProperties("jwt.config")
@Data
public class JwtUtil {

    private String key;
    private long ttl;


    public String createJwt(String id, String subject, Map map){
        long now = System.currentTimeMillis();
        long exp = now+ttl*1000;
        JwtBuilder jwtBuilder =null;
        try {
            jwtBuilder = Jwts.builder().setId(id)
                    .setSubject(subject)
                    .setIssuedAt(new Date())
                    .signWith(SignatureAlgorithm.HS256, key);

            for (Map.Entry stringObjectEntry : map.entrySet()) {
                jwtBuilder.claim(stringObjectEntry.getKey(), stringObjectEntry.getValue());
            }
            if (ttl > 0) {
                jwtBuilder.setExpiration(new Date(exp));
            }
        }catch (Exception e){
            System.err.println(e.getMessage());
        }

        return jwtBuilder.compact();

    }


    public Claims parseJWT(String token){

        Claims     claims = Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(token).getBody();

        return claims;
    }

}

【步骤二】:创建 WebSecurityConfig 类

WebSecurityConfig类 是我们安全认证的关键。它为受保护的资源配置 cors、csrf、会话管理、规则。

package com.zbbmeta.config;

import com.zbbmeta.filter.AuthTokenFilter;
import com.zbbmeta.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Autowired
    private AuthEntryPointJwt unauthorizedHandler;


    @Bean
    public AuthTokenFilter authTokenFilter() {
        return new AuthTokenFilter();
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());

        return authProvider;
    }

    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests().antMatchers("/api/auth
@Slf4j
@Component
public class AuthEntryPointJwt implements AuthenticationEntryPoint {



    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
            throws IOException, ServletException {
        log.error("Unauthorized error: {}", authException.getMessage());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

        final Map body = new HashMap<>();
        body.put("code", HttpServletResponse.SC_UNAUTHORIZED);

        body.put("message", authException.getMessage());
        body.put("path", request.getServletPath());

        final ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(response.getOutputStream(), body);

    }
}

【步骤五】:创建Controller进行登录和注册

package com.zbbmeta.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zbbmeta.api.Result;
import com.zbbmeta.api.ResultCode;
import com.zbbmeta.dto.LoginDto;
import com.zbbmeta.dto.SignupDto;
import com.zbbmeta.entity.Permission;
import com.zbbmeta.entity.Role;
import com.zbbmeta.entity.User;
import com.zbbmeta.entity.UserRole;
import com.zbbmeta.service.RoleService;
import com.zbbmeta.service.UserRoleService;
import com.zbbmeta.service.UserService;
import com.zbbmeta.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.*;
import java.util.stream.Collectors;


@RequestMapping("/api/auth")
@RestController
public class AuthController {

    @Autowired
    UserService userService;

    @Autowired
    RoleService roleService;

    @Autowired
    UserRoleService userRoleService;

    @Autowired
    PasswordEncoder passwordEncoder;
    @Autowired
    private JwtUtil jwtUtil;

    @PostMapping("/signin")
    public Result authenticateUser(@RequestBody LoginDto loginDto) {
        //根据用户名查找用户
        User user = userService.findByName(loginDto.getUsername());
        //不存在表示登录失败
        if(Objects.isNull(user)){
            return Result.FAIL(ResultCode.USERNOEXIT_ERROR);
        }
        //密码不同登录失败
        if(!passwordEncoder.matches(loginDto.getPassword(),user.getPassword())){
            return Result.FAIL(ResultCode.PASSWORD_ERROR);
        }
        List collect = user.getRoles().stream().map(x -> x.getName()).collect(Collectors.toList());
        StringBuilder builder = new StringBuilder();
        List roles = user.getRoles();
        for (Role role : roles) {
            List permissions = role.getPermissions();
            for (Permission permission : permissions) {

                builder.append(permission.getCode()).append(",");
            }
        }
        Map map  = new HashMap<>();
        map.put("username",user.getUsername());
        map.put("permission",builder);
        String token = jwtUtil.createJwt(user.getId(), user.getUsername(), map);

        return Result.SUCCESS(token);
    }



    
    @PostMapping("/signup")
    public Result registerUser( @RequestBody SignupDto signupDto){
        //根据用户名获取用户
        User user = userService.findByName(signupDto.getUsername());
        //用户不是null表示用户已经存在
        if(Objects.nonNull(user)){
            return Result.FAIL(ResultCode.USER_ERROR);
        }
        //添加用户
        User user1 = new User();
        user1.setUsername(signupDto.getUsername());
        user1.setPassword(signupDto.getPassword());

        user1.setPassword(passwordEncoder.encode(signupDto.getPassword()));

        List strRoles = signupDto.getRole();
        List roles = new ArrayList<>();
        //如果没有用户角色,默认添加普通员工
        if (strRoles == null) {
            LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(Role::getName,"普通员工");
            Role role = roleService.getOne(wrapper);
            roles.add(role);
        }else {
            strRoles.forEach(role ->{
                LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
                wrapper.eq(Role::getName,role);
                Role adminRole = roleService.getOne(wrapper);
                roles.add(adminRole);
            });

        }
        //添加用户信息
        boolean save = userService.save(user1);
        String id = user1.getId();

        List userRolesList = new ArrayList<>();
        for (Role role : roles) {
            UserRole userRoles = new UserRole();
            userRoles.setUserId(id);
            userRoles.setRoleId(role.getId());
            userRolesList.add(userRoles);
        }
        //添加用户和角色关系
        userRoleService.saveBatch(userRolesList);
        return Result.SUCCESS("注册成功!");

    }
}

4.测试

图片

注册后的pe_user表格数据如下所示:

图片

图片

因为我们没有登录,所以受保护的资源不能访问

图片

图片

今天,我们在Spring Boot示例中学到关于Spring Security和基于JWT令牌的身份验证的有趣知识。尽管我们写了很多代码,但我希望你能理解应用程序的整体架构,并轻松地将其应用到你的项目中。

## 代码地址
https://github.com/bangbangzhou/spring-boot-backend-example.git
来源:springboot葵花宝典内容投诉

免责声明:

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

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

软考中级精品资料免费领

  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

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

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

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

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

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

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