百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Spring Boot 整合SpringSecurity示例实现权限注解,你了解多少?

nanshan 2024-10-21 06:13 15 浏览 0 评论

引言

一.说明

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

数据表名 中文表名 备注说明 sys_user 系统用户表 基础表 sys_menu 权限表 基础表 sys_role 角色表 基础表 sys_role_menu 角色与权限关系表 中间表 sys_user_role 用户与角色关系表 中间表 Maven依赖如下:

<dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <scope>runtime</scope>
 </dependency>
 <dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <optional>true</optional>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 </dependency>
 <!--Security依赖 -->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-security</artifactId>
 </dependency>
 <!-- MybatisPlus 核心库 -->
 <dependency>
 <groupId>com.baomidou</groupId>
 <artifactId>mybatis-plus-boot-starter</artifactId>
 <version>3.1.0</version>
 </dependency>
 <!-- 引入阿里数据库连接池 -->
 <dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid</artifactId>
 <version>1.1.6</version>
 </dependency>
 <!-- StringUtilS工具 -->
 <dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons-lang3</artifactId>
 <version>3.5</version>
 </dependency>
 <!-- JSON工具 -->
 <dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>fastjson</artifactId>
 <version>1.2.45</version>
 </dependency>
 <!-- JWT依赖 -->
 <dependency>
 <groupId>org.springframework.security</groupId>
 <artifactId>spring-security-jwt</artifactId>
 <version>1.0.9.RELEASE</version>
 </dependency>
 <dependency>
 <groupId>io.jsonwebtoken</groupId>
 <artifactId>jjwt</artifactId>
 <version>0.9.0</version>
 </dependency>
</dependencies>
复制代码

配置如下:

# 配置端口
server:
 port: 8764
spring:
 # 配置数据源
 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
 */
@Slf4j
public 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
 */
@Component
public 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
 */
@Component
public 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
@Component
public 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
@Component
public 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<String,Object> 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
 */
@Component
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {
 /**
 * 用户登出返回结果
 * 这里应该让前端清除掉Token
 * @Author Sans
 * @CreateTime 2019/10/3 9:50
 */
 @Override
 public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){
 Map<String,Object> 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
 */
@Component
public 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<GrantedAuthority> authorities = new HashSet<>();
 // 查询用户角色
 List<SysRoleEntity> 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
 */
@Component
public 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<String> permissions = new HashSet<>();
 List<SysMenuEntity> 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()
 // 取消跨站请求伪造防护
 .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
 */
@Slf4j
public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter {
 public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) {
 super(authenticationManager);
 }
 @Override
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
 // 获得TokenHeader
 String tokenHeader = request.getHeader(JWTConfig.tokenHeader);
 if (null!=tokenHeader && tokenHeader.startsWith(JWTConfig.tokenPrefix)) {
 try {
 // 获取请求头中JWT的Token
 if (!StringUtils.isEmpty(request.getHeader(JWTConfig.tokenHeader))) {
 // 截取JWT前缀
 String token = request.getHeader(JWTConfig.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<GrantedAuthority> authorities = new ArrayList<>();
 String authority = claims.get("authorities").toString();
 if(!StringUtils.isEmpty(authority)){
 List<Map<String,String>> authorityMap = JSONObject.parseObject(authority, List.class);
 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(暂无权限处理类)中进行处理.这里举一些例子,代码中注释有对应的描述.

 表达式 描述 hasRole([role]) 当前用户是否拥有指定角色. hasAnyRole([role1,role2]) 多个角色是一个以逗号进行分隔的字符串如果当前用户拥有指定角色中的任意一个则返回true. hasAuthority([auth]) 等同于hasRole hasAnyAuthority([auth1,auth2]) 等同于hasAnyRole Principle 代表当前用户的principle对象 authentication 直接从SecurityContext获取的当前Authentication对象 permitAll 总是返回true表示允许所有的 denyAll 总是返回false表示拒绝所有的 isAnonymous() 当前用户是否是一个匿名用户 isRememberMe() 表示当前用户是否是通过Remember-Me自动登录的 isAuthenticated() 表示当前用户是否已经登录认证成功了 isFullyAuthenticated() 如果当前用户既不是一个匿名用户又不是通过Remember-Me自动登录的则返回true hasPermission() 使用扩展表达式需要实现PermissionEvaluator接口 /**
 * 管理端信息
 * @Author Sans
 * @CreateTime 2019/10/2 14:22
 * @Return Map<String,Object> 返回数据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<String,Object> 返回数据MAP
 */
 @PreAuthorize("hasAnyRole('ADMIN','USER')")
 @RequestMapping(value = "/list",method = RequestMethod.GET)
 public Map<String,Object> list(){
 Map<String,Object> result = new HashMap<>();
 List<SysUserEntity> 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<String,Object> 返回数据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<SysMenuEntity> 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
 */
@Component
public 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<String> permissions = new HashSet<>();
 List<SysMenuEntity> 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<String,Object> 返回数据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<SysUserEntity> 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<String,Object> 返回数据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<SysRoleEntity> 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

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

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

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

八.项目源码

码云:gitee.com/liselotte/s…

GitHub:github.com/xuyulong201…

小结

欢迎关注头条号:JAVA大飞哥

点击关注评论转发一波~~

私信小编发送“架构”(免费获取JAVA相关的面试架构资料哟)

最后,每一位读到这里的Java程序猿朋友们,感谢你们能耐心地看完。希望在成为一名更优秀的Java程序猿的道路上,我们可以一起学习、一起进步!都能赢取白富美,走向架构师的人生巅峰!

相关推荐

详解 HTTPS、TLS、SSL、HTTP区别和关系

一、什么是HTTPS、TLS、SSLHTTPS,也称作HTTPoverTLS。TLS的前身是SSL,TLS1.0通常被标示为SSL3.1,TLS1.1为SSL3.2,TLS1.2为SSL...

锐安信SSL证书自动化运维系统:灵活管理SSL/TLS证书全生命周期

点击上方关注“锐成云分销”,云建站解决方案专家!域名、SSL证书、DNS、主机一站选齐在SSL/TLS证书的生命周期管理中,证书的各种操作方式是基础且核心的部分之一,更是保障用户数据传输加密的关键。这...

宝塔免费的 SSL/TLS 证书如何续签

申请之前,请确保域名已解析,如未解析会导致审核失败(包括根域名)宝塔SSL申请的是免费版TrustAsiaDVSSLCA-G5证书,仅支持单个域名申请有效期1年,不支持续签,到期后需要重新申...

HTTPS、HTTP、TLS/SSL工作及握手原理、PKI/CA密钥体系

一、HTTPS与HTTP介绍二、TLS/SSL工作原理三、TSL/SSL握手过程四、HTTPS性能优化五、PKI体系一、HTTPS与HTTP介绍1.Https(SecureHypetextTran...

什么是SSL证书卸载 SSL证书卸载有什么作用

SSL证书是数字证书的一种,安装部署的话可以对网站起到身份验证和数据加密的作用。网站部署SSL证书,相对就必然会有SSL证书卸载,那么SSL证书卸载是什么呢?SSL证书卸载有什么作用?随着SSL通信量...

让SSL/TLS协议流行起来:深度解读SSL/TLS实现1

一前言SSL/TLS协议是网络安全通信的重要基石,本系列将简单介绍SSL/TLS协议,主要关注SSL/TLS协议的安全性,特别是SSL规范的正确实现。本系列的文章大体分为3个部分:SSL/TLS协...

苹果、谷歌、微软等一致同意!SSL/TLS证书最长有效期锐减至47天

快科技4月14日消息,苹果此前向CA/B论坛(负责管理SSL/TLS证书的行业组织)提议,将所有证书有效期缩短至45天。日前CA/B论坛服务器证书工作组投票通过SC-081v3提案,最终决定将SSL/...

Android怎么设置端口转发,将访问本设备的端口转到另外一台设备

一、Android系统怎么设置端口转发,将访问本设备的端口转到另外一台设备?要设置端口转发,您需要先在Android设备上安装一个支持端口转发的应用程序。其中一个常用的应用是"Termux&#...

大神级产品:手机装 Linux 运行 Docker 如此简单

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:灵昱Termux作为一个强大的Android终端模拟器,能够运行多种Linux环境。然而,直接在Termux上运行Docker并不可行,需要...

关于H3C交换机的SSH功能配置方法(华三交换机ssh配置)

对于交换机的初步学习,作为初学者的我,还望诸位不吝赐教。若存在不足之处,烦请大家多提宝贵意见。同样身为初学者的我们,亦可携手共进,相互分享技术经验。一、本地用户配置(核心步骤)1.创建用户并设置密码...

Linux常用操作ssh(linux中的ssh命令)

ssh#p是小写ssh-p22user@hostsftp#连接sftp-P22root@host#将文件上传到服务器上:put[本地文件的地址][服务器上文件存储的位置]#将...

小白心得,如何使用SSH连接飞牛系统(fnos)?

一、背景作为一个刚接触飞牛系统的小白,在研究飞牛os的时候,发现很多功能都需要连接ssh,但是如何使用SSH连接飞牛系统成为入门飞牛os的一道坎。下面以自己的学习经历详细记录下过程吧。二、系统设置1、...

如何在 Windows 11 或 10 上使用 Winget 安装 OpenSSH

SSH(SecureShell)是大多数开发人员和系统管理员用来通过Linux远程连接托管服务器或任何云服务的工具,因为SSH在Linux中是内置的。然而,对于Windows呢?是的...

linux文件之ssh配置文件的含义与作用

ssh远程登录命令是操作系统(包括linux和window系统)下常用的操作命令,可以帮助用户,远程登录服务器系统,查看,操作系统相关信息。linux系统对于ssh命令有专门保存其相关配置的目录和文件...

害怕Linux SSH不安全?这几个小妙招安排上!

ssh是访问远程服务器最常用的方法之一,同时,其也是Linux服务器受到攻击的最常见的原因之一。不过别误会...我们并不是说ssh有什么安全漏洞,相反,它在设计上是一个非常安全的协议。但是安...

取消回复欢迎 发表评论: