@builder注解_SpringBoot 整合SpringSecurity示例实现前后分离权限注解+JWT登录认证

一.说明

SpringSecurity是一个用于Java 企业级应用程序的安全框架,主要包含用户认证和用户授权两个方面.相比较Shiro而言,Security功能更加的强大,它可以很容易地扩展以满足更多安全控制方面的需求,但也相对它的学习成本会更高,两种框架各有利弊.实际开发中还是要根据业务和项目的需求来决定使用哪一种.

JWT是在Web应用中安全传递信息的规范,从本质上来说是Token的演变,是一种生成加密用户身份信息的Token,特别适用于分布式单点登陆的场景,无需在服务端保存用户的认证信息,而是直接对Token进行校验获取用户信息,使单点登录更为简单灵活.

二.项目环境

  • SpringBoot版本:2.1.6

  • SpringSecurity版本: 5.1.5

  • MyBatis-Plus版本: 3.1.0

  • JDK版本:1.8

  • 数据表(SQL文件在项目中):数据库中测试号的密码进行了加密,密码皆为123456

29672ff8b26fd57774b07e7db27a538a.png

Maven依赖如下:

<dependencies>        <dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-webartifactId>        dependency>        <dependency>            <groupId>mysqlgroupId>            <artifactId>mysql-connector-javaartifactId>            <scope>runtimescope>        dependency>        <dependency>            <groupId>org.projectlombokgroupId>            <artifactId>lombokartifactId>            <optional>trueoptional>        dependency>        <dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-testartifactId>            <scope>testscope>        dependency>                <dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-securityartifactId>        dependency>                <dependency>            <groupId>com.baomidougroupId>            <artifactId>mybatis-plus-boot-starterartifactId>            <version>3.1.0version>        dependency>                <dependency>            <groupId>com.alibabagroupId>            <artifactId>druidartifactId>            <version>1.1.6version>        dependency>                <dependency>            <groupId>org.apache.commonsgroupId>            <artifactId>commons-lang3artifactId>            <version>3.5version>        dependency>                <dependency>            <groupId>com.alibabagroupId>            <artifactId>fastjsonartifactId>            <version>1.2.45version>        dependency>                <dependency>            <groupId>org.springframework.securitygroupId>            <artifactId>spring-security-jwtartifactId>            <version>1.0.9.RELEASEversion>        dependency>        <dependency>            <groupId>io.jsonwebtokengroupId>            <artifactId>jjwtartifactId>            <version>0.9.0version>        dependency>dependencies>

配置如下:

# 配置端口server:  port: 8764spring:  # 配置数据源  datasource:    driver-class-name: com.mysql.cj.jdbc.Driver    url: jdbc:mysql://localhost:3306/sans_security?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false    username: root    password: 123456    type: com.alibaba.druid.pool.DruidDataSource# JWT配置jwt:  # 密匙KEY  secret: JWTSecret  # HeaderKEY  tokenHeader: Authorization  # Token前缀字符  tokenPrefix: Sans-  # 过期时间 单位秒 1天后过期=86400 7天后过期=604800  expiration: 86400  # 配置不需要认证的接口  antMatchers: /index,/login/**,/favicon.ico# Mybatis-plus相关配置mybatis-plus:  # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)  mapper-locations: classpath:mapper/*.xml  # 以下配置均有默认值,可以不设置  global-config:    db-config:      #主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";      id-type: AUTO      #字段策略 IGNORED:"忽略判断"  NOT_NULL:"非 NULL 判断")  NOT_EMPTY:"非空判断"      field-strategy: NOT_EMPTY      #数据库类型      db-type: MYSQL  configuration:    # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射    map-underscore-to-camel-case: true    # 返回map时true:当查询数据为空时字段返回为null,false:不加这个查询数据为空时,字段将被隐藏    call-setters-on-nulls: true    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

三.编写项目基础类

Entity,Dao,Service,及等SpringSecurity用户的Entity,Service类等在这里省略,请参考源码

编写JWT工具类

/** * JWT工具类 * @Author Sans * @CreateTime 2019/10/2 7:42 */@Slf4jpublic class JWTTokenUtil {    /**     * 生成Token     * @Author Sans     * @CreateTime 2019/10/2 12:16     * @Param  selfUserEntity 用户安全实体     * @Return Token     */    public static String createAccessToken(SelfUserEntity selfUserEntity){        // 登陆成功生成JWT        String token = Jwts.builder()                // 放入用户名和用户ID                .setId(selfUserEntity.getUserId()+"")                // 主题                .setSubject(selfUserEntity.getUsername())                // 签发时间                .setIssuedAt(new Date())                // 签发者                .setIssuer("sans")                // 自定义属性 放入用户拥有权限                .claim("authorities", JSON.toJSONString(selfUserEntity.getAuthorities()))                // 失效时间                .setExpiration(new Date(System.currentTimeMillis() + JWTConfig.expiration))                // 签名算法和密钥                .signWith(SignatureAlgorithm.HS512, JWTConfig.secret)                .compact();        return token;    }}

编写暂无权限处理类

/** * @Description 暂无权限处理类 * @Author Sans * @CreateTime 2019/10/3 8:39 */@Componentpublic class UserAuthAccessDeniedHandler implements AccessDeniedHandler{    /**     * 暂无权限返回结果     * @Author Sans     * @CreateTime 2019/10/3 8:41     */    @Override    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception){        ResultUtil.responseJson(response,ResultUtil.resultCode(403,"未授权"));    }}

编写用户未登录处理类

/** * 用户未登录处理类 * @Author Sans * @CreateTime 2019/10/3 8:55 */@Componentpublic class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint {    /**     * 用户未登录返回结果     * @Author Sans     * @CreateTime 2019/10/3 9:01     */    @Override    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){        ResultUtil.responseJson(response,ResultUtil.resultCode(401,"未登录"));    }}

编写登录失败处理类

/** * @Description 登录失败处理类 * @Author Sans * @CreateTime 2019/10/3 9:06 */@Slf4j@Componentpublic class UserLoginFailureHandler implements AuthenticationFailureHandler {    /**     * 登录失败返回结果     * @Author Sans     * @CreateTime 2019/10/3 9:12     */    @Override    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){        // 这些对于操作的处理类可以根据不同异常进行不同处理        if (exception instanceof UsernameNotFoundException){            log.info("【登录失败】"+exception.getMessage());            ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户名不存在"));        }        if (exception instanceof LockedException){            log.info("【登录失败】"+exception.getMessage());            ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户被冻结"));        }        if (exception instanceof BadCredentialsException){            log.info("【登录失败】"+exception.getMessage());            ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户名密码不正确"));        }        ResultUtil.responseJson(response,ResultUtil.resultCode(500,"登录失败"));    }}

编写登录成功处理类

/** * @Description 登录成功处理类 * @Author Sans * @CreateTime 2019/10/3 9:13 */@Slf4j@Componentpublic class UserLoginSuccessHandler implements AuthenticationSuccessHandler {    /**     * 登录成功返回结果     * @Author Sans     * @CreateTime 2019/10/3 9:27     */    @Override    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){        // 组装JWT        SelfUserEntity selfUserEntity =  (SelfUserEntity) authentication.getPrincipal();        String token = JWTTokenUtil.createAccessToken(selfUserEntity);        token = JWTConfig.tokenPrefix + token;        // 封装返回参数        Map resultData = new HashMap<>();        resultData.put("code","200");        resultData.put("msg", "登录成功");        resultData.put("token",token);        ResultUtil.responseJson(response,resultData);    }}

编写登出成功处理类

/** * 用户登出类 * @Author Sans * @CreateTime 2019/10/3 9:42 */@Componentpublic class UserLogoutSuccessHandler implements LogoutSuccessHandler {    /**     * 用户登出返回结果     * 这里应该让前端清除掉Token     * @Author Sans     * @CreateTime 2019/10/3 9:50     */    @Override    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){        Map resultData = new HashMap<>();        resultData.put("code","200");        resultData.put("msg", "登出成功");        SecurityContextHolder.clearContext();        ResultUtil.responseJson(response,ResultUtil.resultSuccess(resultData));    }}

四.编写Security核心类

编写自定义登录验证类

/** * 自定义登录验证 * @Author Sans * @CreateTime 2019/10/1 19:11 */@Componentpublic class UserAuthenticationProvider implements AuthenticationProvider {    @Autowired    private SelfUserDetailsService selfUserDetailsService;    @Autowired    private SysUserService sysUserService;    @Override    public Authentication authenticate(Authentication authentication) throws AuthenticationException {        // 获取表单输入中返回的用户名        String userName = (String) authentication.getPrincipal();        // 获取表单中输入的密码        String password = (String) authentication.getCredentials();        // 查询用户是否存在        SelfUserEntity userInfo = selfUserDetailsService.loadUserByUsername(userName);        if (userInfo == null) {            throw new UsernameNotFoundException("用户名不存在");        }        // 我们还要判断密码是否正确,这里我们的密码使用BCryptPasswordEncoder进行加密的        if (!new BCryptPasswordEncoder().matches(password, userInfo.getPassword())) {            throw new BadCredentialsException("密码不正确");        }        // 还可以加一些其他信息的判断,比如用户账号已停用等判断        if (userInfo.getStatus().equals("PROHIBIT")){            throw new LockedException("该用户已被冻结");        }        // 角色集合        Set authorities = new HashSet<>();        // 查询用户角色        List sysRoleEntityList = sysUserService.selectSysRoleByUserId(userInfo.getUserId());        for (SysRoleEntity sysRoleEntity: sysRoleEntityList){            authorities.add(new SimpleGrantedAuthority("ROLE_" + sysRoleEntity.getRoleName()));        }        userInfo.setAuthorities(authorities);        // 进行登录        return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);    }    @Override    public boolean supports(Class> authentication) {        return true;    }}

编写自定义PermissionEvaluator注解验证

/** * 自定义权限注解验证 * @Author Sans * @CreateTime 2019/10/6 13:31 */@Componentpublic class UserPermissionEvaluator implements PermissionEvaluator {    @Autowired    private SysUserService sysUserService;    /**     * hasPermission鉴权方法     * 这里仅仅判断PreAuthorize注解中的权限表达式     * 实际中可以根据业务需求设计数据库通过targetUrl和permission做更复杂鉴权     * @Author Sans     * @CreateTime 2019/10/6 18:25     * @Param  authentication  用户身份     * @Param  targetUrl  请求路径     * @Param  permission 请求路径权限     * @Return boolean 是否通过     */    @Override    public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {        // 获取用户信息        SelfUserEntity selfUserEntity =(SelfUserEntity) authentication.getPrincipal();        // 查询用户权限(这里可以将权限放入缓存中提升效率)        Set permissions = new HashSet<>();        List sysMenuEntityList = sysUserService.selectSysMenuByUserId(selfUserEntity.getUserId());        for (SysMenuEntity sysMenuEntity:sysMenuEntityList) {            permissions.add(sysMenuEntity.getPermission());        }        // 权限对比        if (permissions.contains(permission.toString())){            return true;        }        return false;    }    @Override    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {        return false;    }}

编写SpringSecurity核心配置类

/** * SpringSecurity核心配置类 * @Author Sans * @CreateTime 2019/10/1 9:40 */@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限注解,默认是关闭的public class SecurityConfig extends WebSecurityConfigurerAdapter {    /**     * 自定义登录成功处理器     */    @Autowired    private UserLoginSuccessHandler userLoginSuccessHandler;    /**     * 自定义登录失败处理器     */    @Autowired    private UserLoginFailureHandler userLoginFailureHandler;    /**     * 自定义注销成功处理器     */    @Autowired    private UserLogoutSuccessHandler userLogoutSuccessHandler;    /**     * 自定义暂无权限处理器     */    @Autowired    private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;    /**     * 自定义未登录的处理器     */    @Autowired    private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;    /**     * 自定义登录逻辑验证器     */    @Autowired    private UserAuthenticationProvider userAuthenticationProvider;    /**     * 加密方式     * @Author Sans     * @CreateTime 2019/10/1 14:00     */    @Bean    public BCryptPasswordEncoder bCryptPasswordEncoder(){        return new BCryptPasswordEncoder();    }    /**     * 注入自定义PermissionEvaluator     */    @Bean    public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();        handler.setPermissionEvaluator(new UserPermissionEvaluator());        return handler;    }    /**     * 配置登录验证逻辑     */    @Override    protected void configure(AuthenticationManagerBuilder auth){        //这里可启用我们自己的登陆验证逻辑        auth.authenticationProvider(userAuthenticationProvider);    }    /**     * 配置security的控制逻辑     * @Author Sans     * @CreateTime 2019/10/1 16:56     * @Param  http 请求     */    @Override    protected void configure(HttpSecurity http) throws Exception {        http.authorizeRequests()                //不进行权限验证的请求或资源(从配置文件中读取)               .antMatchers(JWTConfig.antMatchers.split(",")).permitAll()                //其他的需要登陆后才能访问                .anyRequest().authenticated()                .and()                //配置未登录自定义处理类                .httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler)                .and()                //配置登录地址                .formLogin()                .loginProcessingUrl("/login/userLogin")                //配置登录成功自定义处理类                .successHandler(userLoginSuccessHandler)                //配置登录失败自定义处理类                .failureHandler(userLoginFailureHandler)                .and()                //配置登出地址                .logout()                .logoutUrl("/login/userLogout")                //配置用户登出自定义处理类                .logoutSuccessHandler(userLogoutSuccessHandler)                .and()                //配置没有权限自定义处理类                .exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler)                .and()                // 开启跨域                .cors()                .and()                // 取消跨站请求伪造防护                .csrf().disable();        // 基于Token不需要session        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);        // 禁用缓存        http.headers().cacheControl();        // 添加JWT过滤器        http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));    }}

五.编写JWT拦截类

编写JWT接口请求校验拦截器

/** * JWT接口请求校验拦截器 * 请求接口时会进入这里验证Token是否合法和过期 * @Author Sans * @CreateTime 2019/10/5 16:41 */@Slf4jpublic class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter {    public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) {        super(authenticationManager);    }    @Override    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {        // 获取请求头中JWT的Token        String tokenHeader = request.getHeader(JWTConfig.tokenHeader);        if (null!=tokenHeader && tokenHeader.startsWith(JWTConfig.tokenPrefix)) {            try {                // 截取JWT前缀                String token = tokenHeader.replace(JWTConfig.tokenPrefix, "");                // 解析JWT                Claims claims = Jwts.parser()                        .setSigningKey(JWTConfig.secret)                        .parseClaimsJws(token)                        .getBody();                // 获取用户名                String username = claims.getSubject();                String userId=claims.getId();                if(!StringUtils.isEmpty(username)&&!StringUtils.isEmpty(userId)) {                    // 获取角色                    List authorities = new ArrayList<>();                    String authority = claims.get("authorities").toString();                    if(!StringUtils.isEmpty(authority)){                        ListString,                        for(Map<String,String> role : authorityMap){                            if(!StringUtils.isEmpty(role)) {                                authorities.add(new SimpleGrantedAuthority(role.get("authority")));                            }                        }                    }                    //组装参数                    SelfUserEntity selfUserEntity = new SelfUserEntity();                    selfUserEntity.setUsername(claims.getSubject());                    selfUserEntity.setUserId(Long.parseLong(claims.getId()));                    selfUserEntity.setAuthorities(authorities);                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(selfUserEntity, userId, authorities);                    SecurityContextHolder.getContext().setAuthentication(authentication);                }            } catch (ExpiredJwtException e){                log.info("Token过期");            } catch (Exception e) {                log.info("Token无效");            }        }        filterChain.doFilter(request, response);        return;    }}

六.权限注解和hasPermission权限扩展

Security允许我们在定义URL方法访问所应有的注解权限时使用SpringEL表达式,在定义所需的访问权限时如果对应的表达式返回结果为true则表示拥有对应的权限,反之则没有权限,会进入到我们配置的UserAuthAccessDeniedHandler(暂无权限处理类)中进行处理.这里举一些例子,代码中注释有对应的描述.

d8e67e7a58249440878decc5155e5f0f.png

    /**     * 管理端信息     * @Author Sans     * @CreateTime 2019/10/2 14:22     * @Return Map 返回数据MAP     */    @PreAuthorize("hasRole('ADMIN')")    @RequestMapping(value = "/info",method = RequestMethod.GET)    public Map<String,Object> userLogin(){        Map<String,Object> result = new HashMap<>();        SelfUserEntity userDetails = SecurityUtil.getUserInfo();        result.put("title","管理端信息");        result.put("data",userDetails);        return ResultUtil.resultSuccess(result);    }    /**     * 拥有ADMIN或者USER角色可以访问     * @Author Sans     * @CreateTime 2019/10/2 14:22     * @Return Map 返回数据MAP     */    @PreAuthorize("hasAnyRole('ADMIN','USER')")    @RequestMapping(value = "/list",method = RequestMethod.GET)    public Map<String,Object> list(){        Map<String,Object> result = new HashMap<>();        List sysUserEntityList = sysUserService.list();        result.put("title","拥有用户或者管理员角色都可以查看");        result.put("data",sysUserEntityList);        return ResultUtil.resultSuccess(result);    }    /**     * 拥有ADMIN和USER角色可以访问     * @Author Sans     * @CreateTime 2019/10/2 14:22     * @Return Map 返回数据MAP     */    @PreAuthorize("hasRole('ADMIN') and hasRole('USER')")    @RequestMapping(value = "/menuList",method = RequestMethod.GET)    public Map<String,Object> menuList(){        Map<String,Object> result = new HashMap<>();        List sysMenuEntityList = sysMenuService.list();        result.put("title","拥有用户和管理员角色都可以查看");        result.put("data",sysMenuEntityList);        return ResultUtil.resultSuccess(result);    }

通常情况下使用hasRole和hasAnyRole基本可以满足大部分鉴权需求,但是有时候面对更复杂的场景上述常规表示式无法完成权限认证,Security也为我们提供了解决方案.通过hasPermission()来扩展表达式.使用hasPermission()首先要实现PermissionEvaluator接口

/** * 自定义权限注解验证 * @Author Sans * @CreateTime 2019/10/6 13:31 */@Componentpublic class UserPermissionEvaluator implements PermissionEvaluator {    @Autowired    private SysUserService sysUserService;    /**     * hasPermission鉴权方法     * 这里仅仅判断PreAuthorize注解中的权限表达式     * 实际中可以根据业务需求设计数据库通过targetUrl和permission做更复杂鉴权     * 当然targetUrl不一定是URL可以是数据Id还可以是管理员标识等,这里根据需求自行设计     * @Author Sans     * @CreateTime 2019/10/6 18:25     * @Param  authentication  用户身份(在使用hasPermission表达式时Authentication参数默认会自动带上)     * @Param  targetUrl  请求路径     * @Param  permission 请求路径权限     * @Return boolean 是否通过     */    @Override    public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {        // 获取用户信息        SelfUserEntity selfUserEntity =(SelfUserEntity) authentication.getPrincipal();        // 查询用户权限(这里可以将权限放入缓存中提升效率)        Set permissions = new HashSet<>();        List sysMenuEntityList = sysUserService.selectSysMenuByUserId(selfUserEntity.getUserId());        for (SysMenuEntity sysMenuEntity:sysMenuEntityList) {            permissions.add(sysMenuEntity.getPermission());        }        // 权限对比        if (permissions.contains(permission.toString())){            return true;        }        return false;    }    @Override    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {        return false;    }}

在请求方法上添加hasPermission示例

    /**     * 拥有sys:user:info权限可以访问     * hasPermission 第一个参数是请求路径 第二个参数是权限表达式     * @Author Sans     * @CreateTime 2019/10/2 14:22     * @Return Map 返回数据MAP     */    @PreAuthorize("hasPermission('/admin/userList','sys:user:info')")    @RequestMapping(value = "/userList",method = RequestMethod.GET)    public Map<String,Object> userList(){        Map<String,Object> result = new HashMap<>();        List sysUserEntityList = sysUserService.list();        result.put("title","拥有sys:user:info权限都可以查看");        result.put("data",sysUserEntityList);        return ResultUtil.resultSuccess(result);    }

hasPermission可以也可以和其他表达式联合使用

    /**     * 拥有ADMIN角色和sys:role:info权限可以访问     * @Author Sans     * @CreateTime 2019/10/2 14:22     * @Return Map 返回数据MAP     */    @PreAuthorize("hasRole('ADMIN') and hasPermission('/admin/adminRoleList','sys:role:info')")    @RequestMapping(value = "/adminRoleList",method = RequestMethod.GET)    public Map<String,Object> adminRoleList(){        Map<String,Object> result = new HashMap<>();        List sysRoleEntityList = sysRoleService.list();        result.put("title","拥有ADMIN角色和sys:role:info权限可以访问");        result.put("data",sysRoleEntityList);        return ResultUtil.resultSuccess(result);    }

七.测试

创建账户这里用户加密使用了Security推荐的bCryptPasswordEncoder方法

 
   /**     * 注册用户     */    @Test    public void contextLoads() {        // 注册用户        SysUserEntity sysUserEntity = new SysUserEntity();        sysUserEntity.setUsername("sans");        sysUserEntity.setPassword(bCryptPasswordEncoder.encode("123456"));        // 设置用户状态        sysUserEntity.setStatus("NORMAL");        sysUserService.save(sysUserEntity);        // 分配角色 1:ADMIN 2:USER        SysUserRoleEntity sysUserRoleEntity = new SysUserRoleEntity();        sysUserRoleEntity.setRoleId(2L);        sysUserRoleEntity.setUserId(sysUserEntity.getUserId());        sysUserRoleService.save(sysUserRoleEntity);    }

登录USER角色账号,登录成功后我们会获取到身份认证的Token

81e55a271ea37356a4176f8e3daf6416.png

访问USER角色的接口,把上一步获取到的Token设置在Headers中,Key为Authorization,我们之前实现的JWTAuthenticationTokenFilter拦截器会根据请求头中的Authorization获取并解析Token

52724bad2960e234c18d57761f006d50.png

使用USER角色Token访问ADMIN角色的接口,会被拒绝,告知未授权(暂无权限会进入我们定义的UserAuthAccessDeniedHandler这个类进行处理)

9d3dd89105b0c382a83e432de5cc5f4c.png

更换ADMIN角色进行登录并访问ADMIN接口

20680f20c1176e42d13e891cfe09efab.png

今天就讲到这里吧,如果觉得不错,记得点赞转发支持一波,谢谢~~~

作者:Sans_

来源:juejin.im/post/5da82f066fb9a04e2a73daec

1095d4d1a5c11e10225e393894267688.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/332505.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

web前端面试问答_Web服务面试问答

web前端面试问答在这篇文章中&#xff0c;我们涵盖了您需要熟悉的Web服务概念的几乎所有部分&#xff0c;以回答任何层级问题。 您还将在分类中找到最佳的Web服务面试问题和答案&#xff0c;因此只有在必要时才可以使用特定条款。 今天可以在各种编程平台上开发各种Web应用程序…

Maven项目的pom.xml文件详解

文章目录pom.xml文件解读标签 dependency 的解读pom.xml文件解读 <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0http://maven.…

u盘装系统学计算机好,教你如何使用u盘做系统

电脑对人们来说是了越来越重要了&#xff0c;怎么说呢&#xff0c;不论是在工作中&#xff0c;还是小孩的学习都需要使用电脑&#xff0c;所以说人们是越来越离不开对电脑的使用了。不过&#xff0c;电脑毕竟是电子产品&#xff0c;所以免不了会出现一些故障问题&#xff0c;比…

使用junit进行单元测试_使用JUnit对ADF应用程序进行单元测试

使用junit进行单元测试JUnit是Java语言的单元测试软件包&#xff0c;由于ADF构建在J2EE框架之上&#xff0c;因此可以用于测试Oracle ADF应用程序。 单元测试基本上是根据某些定义的测试标准来验证最小的可测试模块的过程。 在这里&#xff0c;我将说明如何在JDeveloper 12.2.1…

spring mvc是什么_狂神说SpringMVC01:什么是SpringMVC

狂神说SpringMVC系列连载课程&#xff0c;通俗易懂&#xff0c;基于Spring5版本(视频同步)&#xff0c;欢迎各位狂粉转发关注学习。未经作者授权&#xff0c;禁止转载1、回顾MVC1.1、什么是MVCMVC是模型(Model)、视图(View)、控制器(Controller)的简写&#xff0c;是一种软件设…

Maven的settings.xml范本(含资源仓库镜像配置)

文章目录一、settings.xml 范本二、资源仓库镜像一、settings.xml 范本 <?xml version"1.0" encoding"UTF-8"?> <settings xmlns"http://maven.apache.org/SETTINGS/1.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instanc…

java gradle构建_在Gradle中为JPMS构建Java 6-8库

java gradle构建通过提供Java 9 module-info.class了解如何使用Gradle构建支持JPMS&#xff08; Java平台模块系统 &#xff09;的Java 6-8库。 介绍 如果您需要JPMS本身的介绍&#xff0c;请查看此概述 。 这篇文章主要针对Java库维护者。 任何此类维护人员都必须选择要针对…

tcppwebbrower 关闭安全警报_【知识】锅炉与压力容器安全

一、锅炉的主要安全附件1.安全阀安全阀属于泄压装置&#xff0c;能够自动将锅炉工作压力控制在允许范围之内。当炉内超压时&#xff0c;安全阀自动开启&#xff0c;自动泄压&#xff0c;同时发出警报&#xff1b;当炉内压力降到允许范围时&#xff0c;安全阀自动关闭。安全阀使…

macOS下查看Java/JDK的安装目录_查看Java/JDK的版本信息_配置环境变量JAVA_HOME

文章目录查看JDK的安装目录查看系统当前正在使用的 JDK 版本信息配置环境变量 JAVA_HOME查看JDK的安装目录 通常情况下&#xff0c;macOS安装JDK&#xff0c;默认是安装在 /Library/Java/JavaVirtualMachines 目录下。 使用 java_home 命令查看&#xff1a; [~]$ /usr/libex…

java_home命令详解

文章目录命令介绍命令选项说明帮助信息命令介绍 The java_home command returns a path suitable for setting the JAVA_HOME environment variable. It determines this path from the user’s preferred JVM based on configured environment variables. Additional constra…

安全审计报告_企业做税审的五大好处!税审报告和审计报告的区别

税务审计报告是税务审计人员根据税务审计的相关法律、法规&#xff0c;对被审计单位的纳税行为和纳税事项进行评估&#xff0c;并发表意见的书面文件。(咨询&#xff1a;13510890028&#xff0c;肖)一、企业做税审的五大好处:1、可弥补亏损&#xff0c;减少税款支出企业所得税法…

selenium自动化测试_您如何使用Selenium来计算自动化测试的投资回报率?

selenium自动化测试跨浏览器测试是一种测试&#xff0c;需要大量的精力和时间。 通过不同的浏览器&#xff0c;操作系统&#xff0c;设备&#xff0c;屏幕分辨率测试Web应用程序&#xff0c;以评估针对各种受众的Web内容呈现的过程是一项活动。 特别是如果手动处理。 使用Selen…

早期计算机的应用主要是( ) (2分),计算机基础第1次作业(含答案)

重庆大学网络教育学院《计算机应用基础》2008年秋季第一次作业《计算机基础知识和中文WIN XP操作系统》注&#xff1a;[ ]内为考查知识点一、单选题(每题2分&#xff0c;10题共20分)1、早期的计算机体积大、耗能高、速度慢&#xff0c;其主要原因是制约于( )。[计算机的发展及应…

python输出结果空格分割_用Python编写固定宽度,以空格分隔的CSV输出

我想使用Python的csv编写器编写一个固定宽度,空格分隔和最小引用的CSV文件. 输出示例&#xff1a; item1 item2 "next item1" "next item2" anotheritem1 anotheritem2 如果我用 writer.writerow( (“{0:15s}”.format(item1), “{0:15s}”.format(item2))…

macOS下搭建Nexus 3.x服务器作为Maven的资源仓库服务器

文章目录下载 Nexus 服务器安装包安装 Nexus 服务器配置环境变量/启动服务器/停止服务器Nexus 服务器管理后台登录管理后台Blob StoresNexus 初始密码无法登录的问题仓库&#xff08;Repository&#xff09;Nexus 内置的仓库Repository 的 Type 属性创建代理仓库创建本地仓库&a…

宣城校区计算机排名,2017秋大学计算机(合肥工业大学 宣城校区)

1.《大学计算机》课程与其它类似的课程有什么不同&#xff1f;《大学计算机》是面向大学一年级学生开设的计算机通识类课程&#xff0c;类似的还有《大学计算机基础》、《计算机应用基础》&#xff0c;或者《计算机文化基础》、《信息技术基础》等课程&#xff0c;它和这些不同…

jakarta ee_关于Jakarta EE软件包名称更改的思考

jakarta eeEclipse基金会宣布 Jakarta EE无法继续使用javax软件包名称。 显然&#xff0c;这是由于Java EE以此名称为基础&#xff0c;但不允许对该名称或以该名称开头的类或包进行进一步的修改。 尽管这当然是个坏消息&#xff0c;但对我而言&#xff0c;当宣布Jakarta EE不能…

人工智能教程

参见&#xff1a;https://www.captainbed.net/csdn/

python的setting怎么找_Python的Django框架中settings文件的部署建议

django在一个项目的目录结构划分方面缺乏必要的规范&#xff0c;因此不同人的项目组织形式也千奇百怪&#xff0c;而且也很难说谁的做法就比较好。我根据自己的项目组织习惯&#xff0c;发布了一个项目dj-scaffold。 前些天在reddit上为我的项目dj-scaffold打了个“广告”&…

显示计算机硬盘驱动器更改,计算机更换硬盘驱动器后蓝屏发生了什么

关于更换硬盘后的计算机蓝屏&#xff0c;怎么了&#xff1f;然后让我告诉您更换硬盘后计算机蓝屏的原因和解决方法。希望能对您有所帮助。 &#xff01;计算机更换硬盘后蓝屏的解决方法&#xff1a;蓝屏的主要原因是&#xff1a;1、病毒木马和恶意软件2、有问题的内存(包括物理…