JWT整合SpringSecurity

引入依赖

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <maven.compiler.source>1.7</maven.compiler.source>
  <maven.compiler.target>1.7</maven.compiler.target>

  <swagger.version>3.0.0</swagger.version>
  <springboot.version>2.3.12.RELEASE</springboot.version>
</properties>
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>${springboot.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <version>${springboot.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
    <scope>runtime</scope>    <!--idea中需要指定-->
    <version>${springboot.version}</version>
  </dependency>
  <!--Security 权限依赖-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>${springboot.version}</version>
  </dependency>

  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
    <scope>runtime</scope>
  </dependency>
  <!--springboot整合mybatis的依赖 -->
  <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.1</version>
  </dependency>
  <!--添加mybatis分页插件支持 -->
  <dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.0.0</version>
  </dependency>
  <!-- druid数据库链接池 -->
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.20</version>
  </dependency>
  <!-- 根据springfox-swagger3生成的数据,生成可视化的友好页面 -->
  <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>${swagger.version}</version>
  </dependency>

  <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
  <dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.19.2</version>
  </dependency>
  <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
  <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.20</version>
  </dependency>
</dependencies>

与Mybatis的整合配置

server:
  port: 9001
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/hospitaldb?useUnicode=true;characterEncoding=utf8;useSSL=false;serverTimezone=UTC;
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: org.example.pojo
  configuration:
    use-generated-keys: true

logging:
  level:
    org.example.mapper: debug

实现RBAC权限管理的SQL语句

-- 1、用户信息表
drop table if exists sys_user;
create table sys_user (
  user_id           bigint(20)      not null auto_increment    comment '用户ID',
  dept_id           bigint(20)      default null               comment '部门ID',
  user_name         varchar(64)     default ''                 comment '用户昵称') engine=innodb auto_increment=100 comment = '用户信息表' default charset=utf8;

drop table if exists sys_role;
create table sys_role (
  role_id           bigint(20)      not null auto_increment    comment '角色ID',
  role_name         varchar(64)     not null                   comment '角色名称',
  role_code         varchar(64)    not null                    comment '角色权限字符串',
  primary key (role_id)
) engine=innodb auto_increment=100 comment = '角色信息表' default charset=utf8;

insert into sys_role values('1', '管理员', 'admin');
insert into sys_role values('2', '普通角色',   'common');
insert into sys_role values('3', '超级管理员',   'root');

drop table if exists sys_menu;
create table sys_menu (
  menu_id           bigint(20)      not null auto_increment    comment '菜单ID',
  menu_name         varchar(64)     not null                   comment '菜单名称',
  parent_id         bigint(20)      default 0                  comment '父菜单ID',
  order_num         int(4)          default 0                  comment '显示顺序',
  path              varchar(255)    default '#'                comment '请求地址,菜单URL',
  component         varchar(32)     default ''                 comment '前端页面组件名',
  menu_type         char(1)         default ''                 comment '菜单类型(M目录 C菜单 F按钮)',
  statu             char(1)         default 0                  comment '菜单状态(0显示 1隐藏)',
  permission        varchar(128)    default null               comment '权限标识,如system:user:view',
  primary key (menu_id),
  UNIQUE key `menu_name`(`menu_name`) using BTREE
) engine=innodb auto_increment=2000 comment = '菜单权限表',default CHARSET=utf8;

-- ----------------------------
-- 初始化-菜单信息表数据
-- ----------------------------
-- 一级菜单ome
insert into sys_menu values('1', '系统管理', '0', '1',   '/home',                'HOME', 'M', '0',  '');
insert into sys_menu values('2', '系统工具', '0', '3',  '/tools',               'TOOLS', 'M', '0',  '');
insert into sys_menu values('3', '考试子系统', '0', '3', '/exam',                'EXAM', 'M', '0',  '');
insert into sys_menu values('4', '关于',       '0', '4', 'http://ruoyi.vip', 'menuBlank','C', '0',  '');
-- 二级菜单
insert into sys_menu values('100',  '用户管理', '1', '1', '/system/user',          '', 'C', '0',  'system:user:view');
insert into sys_menu values('101',  '角色管理', '1', '2', '/system/role',          '', 'C', '0',  'system:role:view');
insert into sys_menu values('102',  '菜单管理', '1', '3', '/system/menu',          '', 'C', '0',  'system:menu:view');
insert into sys_menu values('103',  '部门管理', '1', '4', '/system/dept',          '', 'C', '0',  'system:dept:view');
insert into sys_menu values('104',  '岗位管理', '1', '5', '/system/post',          '', 'C', '0',  'system:post:view');
insert into sys_menu values('105',  '字典管理', '1', '6', '/system/dict',          '', 'C', '0',  'system:dict:view');
insert into sys_menu values('106',  '参数设置', '1', '7', '/system/config',        '', 'C', '0',  'system:config:view');
insert into sys_menu values('107',  '通知公告', '1', '8', '/system/notice',        '', 'C', '0');
insert into sys_menu values('108',  '日志管理', '1', '9', '#',                     '', 'M', '0',  '');
insert into sys_menu values('109',  '在线用户', '2', '1', '/monitor/online',       '', 'C', '0',  'monitor:online:view');
insert into sys_menu values('110',  '定时任务', '2', '2', '/monitor/job',          '', 'C', '0',  'monitor:job:view',);

drop table if exists sys_user_role;
create table sys_user_role (
  user_id   bigint(20) not null comment '用户ID',
  role_id   bigint(20) not null comment '角色ID',
  primary key(user_id, role_id)
) engine=innodb comment = '用户和角色关联表';

-- ----------------------------
-- 初始化-用户和角色关联表数据
-- ----------------------------
insert into sys_user_role values ('1', '1');
insert into sys_user_role values ('103', '1');
insert into sys_user_role values ('101', '2');

-- ----------------------------
-- 7、角色和菜单关联表  角色1-N菜单
-- ----------------------------
drop table if exists sys_role_menu;
create table sys_role_menu (
  role_id   bigint(20) not null comment '角色ID',
  menu_id   bigint(20) not null comment '菜单ID',
  primary key(role_id, menu_id)
) engine=innodb comment = '角色和菜单关联表';

-- ----------------------------
-- 初始化-角色和菜单关联表数据
-- ----------------------------
insert into sys_role_menu values ('1', '1');
insert into sys_role_menu values ('1', '2');
insert into sys_role_menu values ('1', '3');
insert into sys_role_menu values ('1', '4');
insert into sys_role_menu values ('1', '100');
insert into sys_role_menu values ('1', '101');
insert into sys_role_menu values ('1', '102');
insert into sys_role_menu values ('1', '103');
insert into sys_role_menu values ('1', '104');
insert into sys_role_menu values ('1', '105');
insert into sys_role_menu values ('1', '106');
insert into sys_role_menu values ('1', '107');
insert into sys_role_menu values ('1', '108');

完成SpringSecurity配置
Spring Security对哪些接口进行保护、什么组件生效、某些功能是否启用等等都需要在配置类中进行配置。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
    //定义放行的url,即白名单
   private static final String[] URL_WHITE_LIST={
         "/login/auth",
         "/login/user",
         "/kaptcha",
         "/hello",
         "/test/pwd",
         "/swagger-ui/",
         "favicon.ico"
   };
   @Autowired
   private LoginFailureHandler loginFailureHandler;
   @Autowired
   private LoginSuccessHandler loginSuccessHandler;
   @Autowired
   private JwtAccessDenidHandler jwtAccessDenidHandler;
   @Autowired
   private MyEntryPoint myEntryPoint;
   @Autowired
   private JwtLogoutSuccessHandle jwtLogoutSuccessHandle;
   //向容器注入JWT过滤器对象
   @Bean
   public JwtFilter jwtFilter() throws Exception {
      return new JwtFilter(authenticationManager());
   }
   //注入加密接口对象
   @Bean("passwordEncoder")
   public PasswordEncoder passwordEncoder(){
       return new BCryptPasswordEncoder();
   }
    //注入授权管理器对象
    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
    //配置security
   @Override
   protected void configure(HttpSecurity http) throws Exception {
      System.out.println("进入spring security 配置...");
      //开启跨域以便前端调用接口
      http.cors().
            and()  //在Security的默认拦截器里,默认会开启CSRF处理,判断请求是否携带了token,如果没有就拒绝访问
            .csrf().disable()  //关闭csrf和frameOptions,如果不关闭会影响前端请求接口
      //登录配置
      .formLogin()
          .successHandler(loginSuccessHandler)
          .failureHandler(loginFailureHandler)
      .and()  //注销退出处理
         .logout()
         .logoutSuccessHandler(jwtLogoutSuccessHandle)
      //禁用session
      .and()
          .sessionManagement()
          .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
      //配置拦截规则,这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护
      .and()
           .authorizeRequests()
           .antMatchers(URL_WHITE_LIST).permitAll()   //放行的URL请求,并授权
           .anyRequest().authenticated()             //这里意思是其它所有接口需要认证才能访问
      // 指定认证错误处理器
      .and()
            .exceptionHandling()
            .authenticationEntryPoint(myEntryPoint)
            .accessDeniedHandler(jwtAccessDenidHandler) //1次性消费类,可使用lambda表达式
      // 配置自定义的过滤器
      .and()
            .addFilter(jwtFilter())
            ;
   }

   @Resource
   private MyUserDetailsServiceImpl userDetailsService;

   //通过构建,注入MyUserDetailsServiceImpl的bean
   @Override
   protected void configure(AuthenticationManagerBuilder builder) throws Exception {
       builder.userDetailsService(userDetailsService);
               //.passwordEncoder(passwordEncoder());
   }
}

完成userDetails类

//用于封装登录用户的名称、密码、权限列表信息
public class MyUserDetails extends User {

    public MyUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }
}

完成MyUserDetailsServiceImpl
定义MyUserDetailsMapper,通过用户名称、id,查询用户信息,获取角色、权限列表。

public interface MyUserDetailsMapper {
	//根据userID查询用户信息
    @Select("SELECT login_name,password,statu\n" +
            "FROM sys_user u\n" +
            "WHERE u.login_name = #{userId}")
    MyUserDetails findByUserName(@Param("userId") String userId);

    //根据userName查询用户角色
    @Select("SELECT role_code\n" +
            "FROM sys_role r\n" +
            "LEFT JOIN sys_user_role ur ON r.role_id = ur.role_id\n" +
            "LEFT JOIN sys_user u ON u.user_id = ur.user_id\n" +
            "WHERE u.user_name = #{userName}")
    List<String> findRoleByUserName(@Param("userId") String userName);
    
    //根据userID查询用户角色
    @Select("SELECT role_code\n" +
    		"FROM sys_role r\n" +
    		"LEFT JOIN sys_user_role ur ON r.role_id = ur.role_id\n" +
    		"LEFT JOIN sys_user u ON u.user_id = ur.user_id\n" +
    		"WHERE u.user_id = #{userId}")
    List<String> findRoleByUserId(@Param("userId") int userId);


    //根据用户角色查询用户权限
    @Select({
      "<script>",
         "SELECT permission " ,
         "FROM sys_menu m " ,
         "LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id " ,
         "LEFT JOIN sys_role r ON r.role_id = rm.role_id ",
         "WHERE r.role_code IN ",
         "<foreach collection='roleCodes' item='roleCode' open='(' separator=',' close=')'>",
            "#{roleCode}",
         "</foreach>",
      "</script>"
    })
    List<String> findAuthorityByRoleCodes(@Param("roleCodes") List<String> roleCodes);
}

完成 MyUserDetailsServiceImpl类,这里使用了前面定义的MyUserDetails 类。

@Component //
public class MyUserDetailsServiceImpl implements UserDetailsService{
	@Resource
	private MyUserDetailsMapper  myUserDetailsMapper;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		//1.获得用户信息
		MyUserDetails myUserDetails=myUserDetailsMapper.findByUserName(username);
		if (myUserDetails==null){
			throw new UsernameNotFoundException("用户名不存在!");
		}
		//2.获得用户角色列表(一个用户可能有多个角色
		List<String> roles=myUserDetailsMapper.findRoleByUserName(username);
		//3.通过角色列表获取权限列表(一个角色可能有多个权限
		List<String> authorities=myUserDetailsMapper.findAuthorityByRoleCodes(roles);
	    //4.为角色标识加上ROLE_前缀(Spring Security规范)
		roles=roles.stream()
				.map(rc->"ROLE"+rc)
				.collect(Collectors.toList());
	    //5.角色是一种特殊的权限,所以合并
		authorities.addAll(roles);
		//转成用逗号分隔的字符串,为用户设置权限标识		myUserDetails.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(
				String.join(",", authorities)));
		return myUserDetails;
	}
	public List<GrantedAuthority> getAuthority(int userId){
		//2.获得用户角色列表(一个用户可能有多个角色
		List<String> roles=myUserDetailsMapper.findRoleByUserId(userId);
		System.out.println(roles);
		//3.通过角色列表获取权限列表(一个角色可能有多个权限
		List<String> authorities=myUserDetailsMapper.findAuthorityByRoleCodes(roles);
		//4.为角色标识加上ROLE_前缀(Spring Security规范)
		roles=roles.stream()
				.map(rc->"ROLE_"+rc)
				.collect(Collectors.toList());
		//5.角色是一种特殊的权限,所以合并
		authorities.addAll(roles);
		System.out.println(authorities);
		//转成用逗号分隔的字符串,为用户设置权限标识
		List<GrantedAuthority> authoritiesList=AuthorityUtils.commaSeparatedStringToAuthorityList(
				String.join(",", authorities));
		return authoritiesList;
	}
}

整合JWT

public class JwtFilter extends BasicAuthenticationFilter {
    @Resource
    private UserService userService;
    @Resource
    private MyUserDetailsServiceImpl userDetailsService;

    public JwtFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }
    //对http请求过滤处理
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //1。取出请求头中包含的token
        String token=request.getHeader("Authorization");
        if (token==null || token.length()<1){//token不合法,过滤放行
            chain.doFilter(request,response);
            return;
        }
        //2.解析token,进行token合法性验证
        JWT jwtToken= JWTUtil.parseToken(token);
        if (jwtToken.verify()){
            throw new JWTException("无效的token,请重新登录");
        }
        //3.合法的token,则对用户进行权限鉴定
        //从token中解析出登录用户名称
        String username=jwtToken.getPayload(ConstUtil.JWT_TOKEN_LOAD_NAME).toString();
        System.out.println("JWT,解析token,用户:"+username);
        User user=userService.getByName(username);
        if (user==null){
            throw new JWTException("无效的token,用户不存在!");
        }
        //生成授权对象,包含用户名称、密码、权限列表
        Authentication authentication=new UsernamePasswordAuthenticationToken(username,
                user.getPassword(),userDetailsService.getAuthorities(user));
        //把授权对象加入security context中
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request,response);//过滤器连继续处理其它请求
    }
}

登录处理

//用户登录处理
@PostMapping("/login/auth")
public ResponseResult login(@RequestBody User user){//注解传入json格式对象数据
    System.out.println(user);
    ResponseResult result = new ResponseResult(-1,"登录失败",null);
    //请求业务,查询注册用户
    User signinUser=userService.findUserByNamePwd(user.getName(),user.getPassword());
    if (signinUser!=null){
        //生成token
        String token=createToken(signinUser);
        //生成UserVo对象,封装了token及签名用户信息
        result.success("登录成功",new UserVo(token,signinUser));
    }
    return result;
}
//使用hutool工具包来生成token
private String createToken(User user) {
    DateTime now = DateTime.now();
    DateTime newTime = now.offsetNew(DateField.MINUTE, ConstUtil.JWT_EXPIRE_TIME);

    Map<String,Object> payload = new HashMap<String,Object>();
    //签发时间
    payload.put(JWTPayload.ISSUED_AT, now);
    //过期时间
    payload.put(JWTPayload.EXPIRES_AT, newTime);
    //生效时间
    payload.put(JWTPayload.NOT_BEFORE, now);
    //载荷
    payload.put(ConstUtil.JWT_TOKEN_LOAD_NAME, user.getName());
    payload.put(ConstUtil.JWT_TOKEN_LOAD_PASSWORD, user.getPassword());

    String key = ConstUtil.JWT_TOKEN_KEY;
    String token = JWTUtil.createToken(payload, key.getBytes());
    //System.out.println(token);
    return token;
}

整合完成。
下面来看看security内置注解的用法
使用security内置权限注解

Security权限注解
@PreAuthorize:在方法执行前进行权限检查
@PosAuthorize:在方法执行后进行权限检查
@Secured:类似@PreAuthorize

在代码中使用security权限注解


```csharp

```csharp
//添加用户
@PostMapping("/user")
@Secured("hasAuthority('system:user:add')")  //要求操作用户具有‘system:user:add’权限
public ResponseResult addUser(@RequestBody User user){  //传入json数据
//public ResponseResult addUser(User user){  //传入form表单数据
    System.out.println("add user!");
    System.out.println(user);
    ResponseResult result=new ResponseResult(-1,"添加用户失败",false);
    //调业务方法
    boolean bn=userService.add(user);
    if (bn){
        result.success(true);
    }
    return result;
}
//删除用户
@PreAuthorize("hasRole('admin')") //要求用户具有admin角色权限
@DeleteMapping("/user/{id}")
public ResponseResult deleteUser(@PathVariable Integer id){
    ResponseResult result=new ResponseResult(-1,"删除失败",false);
    boolean bn=userService.delete(id);
    if (bn){
        return result.success(bn);
    }
    return result;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值