springboot整合jwt与vue的后端管理系统5

然后接着讲解决授权

解决授权

问题1:我们是在哪里赋予用户权限的?有两个地方:

  • 1、用户登录,调用调用UserDetailsService.loadUserByUsername()方法时候可以返回用户的权限信息。
  • 2、接口调用进行身份认证过滤器时候JWTAuthenticationFilter,需要返回用户权限信息

问题2:在哪里决定什么接口需要什么权限?

Security内置的权限注解:

可以在Controller的方法前添加这些注解表示接口需要什么权限。

比如需要Admin角色权限:

@PreAuthorize("hasRole('admin')")

比如需要添加管理员的操作权限

@PreAuthorize("hasAuthority('sys:user:save')")

流程清晰之后我们就开始我们的编码: 

  • UserDetailsServiceImpl
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
   ...   
   return new AccountUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), getUserAuthority(sysUser.getId()));
}
public List<GrantedAuthority> getUserAuthority(Long userId) {
   // 通过内置的工具类,把权限字符串封装成GrantedAuthority列表
   return  AuthorityUtils.commaSeparatedStringToAuthorityList(
         sysUserService.getUserAuthorityInfo(userId)
   );
}

com.rao.security.JWTAuthenticationFilter

SysUser sysUser = sysUserService.getByUsername(username);
List<GrantedAuthority> grantedAuthorities = userDetailsService.getUserAuthority(sysUser.getId());
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
      = new UsernamePasswordAuthenticationToken(username, null, grantedAuthorities);

 代码中的com.rao.service.impl.SysUserServiceImpl#getUserAuthorityInfo是重点:

package com.rao.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.rao.entity.SysMenu;
import com.rao.entity.SysRole;
import com.rao.entity.SysUser;
import com.rao.mapper.SysUserMapper;
import com.rao.service.SysMenuService;
import com.rao.service.SysRoleService;
import com.rao.service.SysUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rao.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 饶小兵
 * @since 2022-03-09
 */
@Slf4j
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

@Autowired
    SysRoleService sysRoleService;
    @Autowired
    RedisUtil redisUtil;
    @Autowired
    SysMenuService sysMenuService;
    @Autowired
    SysUserMapper sysUserMapper;
    @Override
    public SysUser getByUsername(String username) {
       return getOne(new QueryWrapper<SysUser>().eq("username", username));
    }

    public String getUserAuthoriyInfo(Long id) {


        SysUser sysUser = this.getById(id);

        String authority=null;
        if (redisUtil.hasKey("GrantedAuthority:"+sysUser.getUsername())){
            authority = (String) redisUtil.get("GrantedAuthority" + sysUser.getUsername());
        }else{
            List<SysRole> roles = sysRoleService.list
                    (new QueryWrapper<SysRole>().inSql("id", "select role_id from sys_user_role where user_id = " + id));
            if (roles.size()>0){
                String roleNames = roles.stream().map(r -> "ROLE_" + r.getCode()).collect(Collectors.joining(","));

               authority=roleNames.concat(",");
            }

            List<Long> MenuIds = sysUserMapper.getNavMenuIds(id);
            if (MenuIds.size()>0){
                List<SysMenu> sysMenus = sysMenuService.listByIds(MenuIds);
                String permName = sysMenus.stream().map(m -> m.getPerms()).collect(Collectors.joining(","));
                authority=authority.concat(permName);
            }
            log.info("用户ID-{}--拥有的权限:{}",id,authority);

            redisUtil.set("GrantedAuthority"+sysUser.getUsername(),authority,60*60);
        }
     return  authority;
    }

    @Override
    public void clearUserAuthorityInfo(String username) {
        redisUtil.del("GrantedAuthority"+username);
    }

    @Override
    public void clearUserAuthorityInfoByRoleId(Long roleId) {
        List<SysUser> sysUsers = this.list(new QueryWrapper<SysUser>().inSql("id", "select user_id from sys_user_role where role_id = " + roleId));
        sysUsers.forEach(u->{
            this.clearUserAuthorityInfo(u.getUsername());
        });
    }
  //
    @Override
    public void clearUserAuthorityInfoByMenuId(Long menuId) {
        List<SysUser> sysUsers = sysUserMapper.listByMenuId(menuId);
        sysUsers.forEach(u->{
            this.clearUserAuthorityInfo(u.getUsername());
        });
    }
}

 

可以看到,我通过用户id分别获取到用户的角色信息和菜单信息,然后通过逗号链接起来,因为角色信息我们需要这样“ROLE_”+角色,所以才有了上面的写法:
比如用户拥有Admin角色和添加用户权限,则最后的字符串是:ROLE_admin,syssave

同时为了避免多次查库,我做了一层缓存,这里理解应该不难。

然后sysUserMapper.getNavMenuIds(userId)因为要查询数据库,具体SQL如下:

  • com.rao.mapper.SysUserMapper#getNavMenuIds
<select id="getNavMenuIds" resultType="java.lang.Long">
    SELECT
        DISTINCT rm.menu_id
    FROM
        sys_user_role ur
    LEFT JOIN `sys_role_menu` rm ON rm.role_id = ur.role_id
    WHERE
        ur.user_id = #{userId};
</select>

上面表示通过用户ID获取用户关联的菜单的id,因此需要用到两个中间表的关联了。
ok,这样我们就赋予了用户角色和操作权限了。后面我们只需要在Controller添加上具体注解表示需要的权限,Security就会自动帮我们自动完成权限校验了。

权限缓存

因为上面我在获取用户权限那里添加了个缓存,这时候问题来了,就是权限缓存的实时更新问题,比如当后台更新某个管理员的权限角色信息的时候如果权限缓存信息没有实时更新,就会出现操作无效的问题,那么我们现在点定义几个方法,用于清除某个用户或角色或者某个菜单的权限的方法: 

 

上面最后一个方法查到了与菜单关联的所有用户的,具体sql如下:

  • com.rao.mapper.SysUserMapper#listByMenuId
<select id="listByMenuId" resultType="com.javacat.entity.SysUser">
    SELECT
    DISTINCT
        su.*
    FROM
        sys_user_role ur
    LEFT JOIN `sys_role_menu` rm ON rm.role_id = ur.role_id
    LEFT JOIN `sys_user` su ON su.id = ur.user_id
    WHERE
        rm.menu_id = #{menuId};
</select>

退出数据返回

jwt -username

token - 随机码 - redis

  • com.rao.security.JwtLogoutSuccessHandler

 

package com.rao.security;

import cn.hutool.json.JSONUtil;
import com.rao.common.lang.Result;
import com.rao.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Component
public class JwtLogoutSuccessHandler implements LogoutSuccessHandler {
    @Autowired
    JwtUtils jwtUtils;

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
         if (authentication!=null){
             new SecurityContextLogoutHandler().logout(request,response,authentication);
         }
        response.setContentType("application/json;charset=UTF-8");
        response.setHeader(jwtUtils.getHeader(),"");
        ServletOutputStream outputStream = response.getOutputStream();
        Result result = Result.succ("");
        outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();



    }
}

无权限数据返回

  • com.rao.security.JwtAccessDeniedHandler
package com.rao.security;

import cn.hutool.json.JSONUtil;
import com.rao.common.lang.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Slf4j

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
         log.info("权限不够");
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        ServletOutputStream outputStream = response.getOutputStream();
        Result result = Result.fail(accessDeniedException.getMessage());
        outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

 

解决跨域问题

上面的调试我们都是使用的postman,如果我们和前端进行对接的时候,会出现跨域的问题,如何解决?

  • com.rao.config.CorsConfig
package com.rao.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addExposedHeader("Authorization");
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
//          .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                .maxAge(3600);
    }
}

 到了这里我们对用户和权限分配写好了下节就讲具体实现了相当于前面是搭后端框架

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringBoot整合JWT是一种常用的实现权限验证功能的方式。JWT(Json Web Token)是一种基于JSON的开放标准,用于在网络应用环境中传递声明。在SpringBoot项目中整合JWT,可以实现用户身份验证和访问控制的功能。 整合JWT的步骤如下: 1. 在项目的pom文件中添加JWT依赖。可以使用com.auth0的java-jwt库,具体的依赖配置如下: ```xml <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency> ``` 2. 创建用户实体类。可以使用@Data注解自动生成getter和setter方法,示例如下: ```java package com.example.manageserve.controller.dto; import lombok.Data; @Data public class UserDTO { private String username; private String password; private String nickname; private String token; } ``` 3. 将拦截器注入到SpringMVC。创建一个配置类,实现WebMvcConfigurer接口,并重写addInterceptors方法,如下所示: ```java package com.example.manageserve.config; import com.example.manageserve.config.interceptor.JwtInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry){ registry.addInterceptor(jwtInterceptor()) .addPathPatterns("/**") //拦截所有请求,通过判断token是否合法来决定是否需要登录 .excludePathPatterns("/user/login","/user/register"); } @Bean public JwtInterceptor jwtInterceptor(){ return new JwtInterceptor(); } } ``` 通过以上步骤,我们可以实现SpringBootJWT整合,实现了用户的登录和权限验证功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [xmljava系统源码-JWT-DEMO:SpringBoot整合JWT完成权限验证功能示例](https://download.csdn.net/download/weixin_38641764/19408331)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [springboot集成JWT](https://blog.csdn.net/weixin_67958017/article/details/128856282)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值