若依解析(一)登录认证流程

JWT+SpringSecurity 6.X 实现登录

JWT token只包含uuid ,token 解析uuid,然后某个常量加UUID 从Redis缓存查询用户信息
流程图如下

登录流程简画

感谢若依,感谢开源,能有这么好系统供我学习。

设计数据库,部门表,用户表,角色表,权限菜单表,用户角色表中间表,部门角色中间表,角色权限菜单表中间表

登录成功返回前端

{"msg": "操作成功","code": "200",//用户tokentoken:"XXXXX",//菜单权限"permissions": ["system:user:resetPwd","system:post:list","monitor:operlog:export","monitor:druid:list","system:menu:query","system:dept:remove","system:menu:list",..."tool:gen:edit"],//角色权限"roles": ["test2","test1"],//用户信息"user": {"createBy": "admin","createTime": "2024-11-28 10:06:12",}//路由信息[{"name": "System","path": "/system","hidden": false,"redirect": "noRedirect","component": "Layout","alwaysShow": true,"meta": {"title": "系统管理","icon": "system","noCache": false,"link": null},"children": [{"name": "User","path": "user","hidden": false,"component": "system/user/index","meta": {"title": "用户管理","icon": "user","noCache": false,"link": null}},{"name": "Log","path": "log","hidden": false,"redirect": "noRedirect","component": "ParentView","alwaysShow": true,"meta": {"title": "日志管理","icon": "log","noCache": false,"link": null},"children": [{"name": "Operlog","path": "operlog","hidden": false,"component": "monitor/operlog/index","meta": {"title": "操作日志","icon": "form","noCache": false,"link": null}},{"name": "Logininfor","path": "logininfor","hidden": false,"component": "monitor/logininfor/index","meta": {"title": "登录日志","icon": "logininfor","noCache": false,"link": null}}]}]},{"name": "Tool","path": "/tool","hidden": false,"redirect": "noRedirect","component": "Layout","alwaysShow": true,"meta": {"title": "系统工具","icon": "tool","noCache": false,"link": null},"children": [{"name": "Build","path": "build","hidden": false,"component": "tool/build/index","meta": {"title": "表单构建","icon": "build","noCache": false,"link": null}},{"name": "Gen","path": "gen","hidden": false,"component": "tool/gen/index","meta": {"title": "代码生成","icon": "code","noCache": false,"link": null}},{"name": "Swagger","path": "swagger","hidden": false,"component": "tool/swagger/index","meta": {"title": "系统接口","icon": "swagger","noCache": false,"link": null}}]},{"name": "Http://ruoyi.vip","path": "http://ruoyi.vip","hidden": false,"component": "Layout","meta": {"title": "若依官网","icon": "guide","noCache": false,"link": "http://ruoyi.vip"}}]
}

所有实体的基类

/*** 实体基类**/
@Setter
@Getter
@ToString
public class BaseEntity 
{private static final long serialVersionUID = 1L;/** 搜索值 */private String searchValue;/** 创建者 */private String createBy;/** 创建时间 */@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date createTime;/** 更新者 */private String updateBy;/** 更新时间 */@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date updateTime;/** 备注 */@ApiModelProperty(value = "员工姓名",hidden = true)private String remark;/*** 更新数据**/@ApiModelProperty(value = "员工姓名",hidden = true)private boolean update;/*** 新增数据*/@ApiModelProperty(value = "员工姓名",hidden = true)private boolean add;/** 请求参数 */@JsonInclude(JsonInclude.Include.NON_EMPTY)@ApiModelProperty(value = "请求参数",hidden = true)private Map<String, Object> params;}

部门实体

/*** 部门表 sys_dept**/
@Setter
@Getter
@ToString
public class SysDept extends BaseEntity
{private static final long serialVersionUID = 1L;/** 部门ID */private Long deptId;/** 父部门ID */private Long parentId;/** 祖级列表 */private String ancestors;/** 部门名称 */private String deptName;/** 显示顺序 */private Integer orderNum;/** 负责人 */private String leader;/** 联系电话 */private String phone;/** 邮箱 */private String email;/** 部门状态:0正常,1停用 */private String status;/** 删除标志(0代表存在 2代表删除) */private String delFlag;/** 父部门名称 */private String parentName;/** 子部门 */private List<SysDept> children = new ArrayList<SysDept>();
}

用户实体包含角色集合,以及所属部门信息

/*** 用户对象 sys_user**/
@Setter
@Getter
@ToString
public class SysUser extends BaseEntity
{private static final long serialVersionUID = 1L;/** 用户ID */private Long userId;/** 部门ID */private Long deptId;/** 用户账号 */private String userName;/** 用户昵称 */private String nickName;/** 用户邮箱 */private String email;/** 手机号码 */private String phonenumber;/** 用户性别 */private String sex;/** 用户头像 */private String avatar;/** 密码 */private String password;/** 帐号状态(0正常 1停用) */private String status;/** 删除标志(0代表存在 2代表删除) */private String delFlag;/** 最后登录IP */private String loginIp;/** 最后登录时间 */private Date loginDate;/** 部门对象 */private SysDept dept;/** 角色对象 */private List<SysRole> roles;/** 角色组 */private Long[] roleIds;/** 岗位组 */private Long[] postIds;/** 角色ID */private Long roleId;}

角色实体包含权限按钮集合,每个按钮对应一个权限(system:user:add),所以角色还包含权限集合

/*** 角色表 sys_role**/
@Setter
@Getter
@ToString
public class SysRole extends BaseEntity
{private static final long serialVersionUID = 1L;/** 角色ID */private Long roleId;/** 角色名称 */private String roleName;/** 角色权限 */private String roleKey;/** 角色排序 */private Integer roleSort;/** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */private String dataScope;/** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */private boolean menuCheckStrictly;/** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */private boolean deptCheckStrictly;/** 角色状态(0正常 1停用) */private String status;/** 删除标志(0代表存在 2代表删除) */private String delFlag;/** 用户是否存在此角色标识 默认不存在 */private boolean flag = false;/** 菜单组 */private Long[] menuIds;/** 部门组(数据权限) */private Long[] deptIds;/** 角色菜单权限 */private Set<String> permissions;}

按钮权限实体,一个按钮下面还有子菜单,所以还包含一个菜单集合

/*** 菜单权限表 sys_menu**/
@Setter
@Getter
@ToString
public class SysMenu extends BaseEntity
{private static final long serialVersionUID = 1L;/** 菜单ID */private Long menuId;/** 菜单名称 */private String menuName;/** 父菜单名称 */private String parentName;/** 父菜单ID */private Long parentId;/** 显示顺序 */private Integer orderNum;/** 路由地址 */private String path;/** 组件路径 */private String component;/** 路由参数 */private String query;/** 路由名称,默认和路由地址相同的驼峰格式(注意:因为vue3版本的router会删除名称相同路由,为避免名字的冲突,特殊情况可以自定义) */private String routeName;/** 是否为外链(0是 1否) */private String isFrame;/** 是否缓存(0缓存 1不缓存) */private String isCache;/** 类型(M目录 C菜单 F按钮) */private String menuType;/** 显示状态(0显示 1隐藏) */private String visible;/** 菜单状态(0正常 1停用) */private String status;/** 权限字符串 */private String perms;/** 菜单图标 */private String icon;/** 子菜单 */private List<SysMenu> children = new ArrayList<SysMenu>();}

登录用户省份鉴权实体,继承UserDetails来源于 spring security

这个用户包含数据库中用户信息,而用户信息又包含角色,而角色又包含权限菜单,所以这个自定义UserDetails实现类包含用户的所有信息

/*** 登录用户身份权限* */
@Setter
@Getter
@ToString
public class LoginUser implements UserDetails
{private static final long serialVersionUID = 1L;/*** 用户ID*/private Long userId;private String userName;/*** 部门ID*/private Long deptId;/*** 用户唯一标识*/private String token;/*** 登录时间*/private Long loginTime;/*** 过期时间*/private Long expireTime;/*** 登录IP地址*/private String ip;/*** 登录地点*/private String loginLocation;/*** 浏览器类型*/private String browser;/*** 操作系统*/private String os;/*** 权限列表*/private Set<String> permissions;/*** 用户信息*/private SysUser user;@JSONField(serialize = false)@Overridepublic String getPassword(){return user.getPassword();}@Overridepublic String getUsername(){return user.getUserName();}/*** 账户是否未过期,过期无法验证*/@JSONField(serialize = false)@Overridepublic boolean isAccountNonExpired(){return true;}/*** 指定用户是否解锁,锁定的用户无法进行身份验证* * @return*/@JSONField(serialize = false)@Overridepublic boolean isAccountNonLocked(){return true;}/*** 指示是否已过期的用户的凭据(密码),过期的凭据防止认证* * @return*/@JSONField(serialize = false)@Overridepublic boolean isCredentialsNonExpired(){return true;}/*** 是否可用 ,禁用的用户不能身份验证* * @return*/@JSONField(serialize = false)@Overridepublic boolean isEnabled(){return true;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities(){return null;}
}

导入spring-security 6.x 依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

spring-security 上下文,上下文管理对象,用来在程序任何地方获取Authentication

/***  自定义 spring security 上下文管理类,管理Authentication对象**/
public class AuthenticationContextHolder
{//线程池保证线程传递同一个Authentication对象private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();public static Authentication getContext(){return contextHolder.get();}public static void setContext(Authentication context){contextHolder.set(context);}public static void clearContext(){contextHolder.remove();}
}

登录验证

/*** 登录验证** @param username 用户名* @param password 密码* @param code 验证码* @param uuid 唯一标识* @return 结果*/public String login(String username, String password, String code, String uuid){// 验证码校验validateCaptcha(username, code, uuid);// 登录前置校验loginPreCheck(username, password);// 用户验证Authentication authentication = null;try{//创建未认证令牌UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);AuthenticationContextHolder.setContext(authenticationToken);// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername,真正通过用户名查询数据库,然后在比较密码//密码一致,将UserDetail 对象转换为Authentication 对象authentication = authenticationManager.authenticate(authenticationToken);}catch (Exception e){if (e instanceof BadCredentialsException){AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));throw new UserPasswordNotMatchException();}else{AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));throw new ServiceException(e.getMessage());}}finally{AuthenticationContextHolder.clearContext();}AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));LoginUser loginUser = (LoginUser) authentication.getPrincipal();recordLoginInfo(loginUser.getUserId());// 验证成功生成tokenreturn tokenService.createToken(loginUser);}

自定义UserDetailsServiceImpl,就是验证登录密码与从数据库中通过用户名查询的登录密码是否一致,一致的话再查询用户所有权限,

在把用户信息封装成UserDetail 返回

/*** 用户验证处理**/
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);@Autowiredprivate ISysUserService userService;@Autowiredprivate SysPasswordService passwordService;@Autowiredprivate SysPermissionService permissionService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{SysUser user = userService.selectUserByUserName(username);if (StringUtils.isNull(user)){log.info("登录用户:{} 不存在.", username);throw new ServiceException(MessageUtils.message("user.not.exists"));}else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())){log.info("登录用户:{} 已被删除.", username);throw new ServiceException(MessageUtils.message("user.password.delete"));}else if (UserStatus.DISABLE.getCode().equals(user.getStatus())){log.info("登录用户:{} 已被停用.", username);throw new ServiceException(MessageUtils.message("user.blocked"));}//验证登录密码与通过登录用户查询到的密码是否一致passwordService.validate(user);//密码验证成功,查询用户权限 ,将用户的所有信息封装成UserDetails 对象在返回return createLoginUser(user);}public UserDetails createLoginUser(SysUser user){//查询用户权限,返回 LoginUserreturn new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));}
}

查询用户权限

/*** 获得用户权限**/
@Component
public class SysPermissionService
{@Autowiredprivate ISysRoleService roleService;@Autowiredprivate ISysMenuService menuService;/*** 获取角色数据权限** @param user 用户信息* @return 角色权限信息*/public Set<String> getRolePermission(SysUser user){Set<String> roles = new HashSet<String>();// 管理员拥有所有权限if (user.isAdmin()){roles.add("admin");}else{roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));}return roles;}/*** 获取菜单数据权限** @param user 用户信息* @return 菜单权限信息*/public Set<String> getMenuPermission(SysUser user){Set<String> perms = new HashSet<String>();// 管理员拥有所有权限if (user.isAdmin()){perms.add("*:*:*");}else{List<SysRole> roles = user.getRoles();if (!CollectionUtils.isEmpty(roles)){// 多角色设置permissions属性,以便数据权限匹配权限for (SysRole role : roles){if (StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL)){//通过角色获取角色下的菜单权限Set<String> rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId());role.setPermissions(rolePerms);perms.addAll(rolePerms);}}}else{perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));}}return perms;}
}

登录密码验证

/*** 验证登录密码**/
@Component
public class SysPasswordService
{@Autowiredprivate RedisCache redisCache;//最大错误登录次数@Value(value = "${user.password.maxRetryCount}")private int maxRetryCount;//最大错误登录次数已满,固定时间才能重新登录@Value(value = "${user.password.lockTime}")private int lockTime;/*** 登录账户密码错误次数缓存键名** @param username 用户名* @return 缓存键key*/private String getCacheKey(String username){return CacheConstants.PWD_ERR_CNT_KEY + username;}public void validate(SysUser user){Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();String username = usernamePasswordAuthenticationToken.getName();String password = usernamePasswordAuthenticationToken.getCredentials().toString();//获取用户名密码错误登录次数Integer retryCount = redisCache.getCacheObject(getCacheKey(username));if (retryCount == null){retryCount = 0;}//用户名密码错误登录次数 大于最大登录次数返回提示10分钟后登录if (retryCount >= Integer.valueOf(maxRetryCount).intValue()){throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);}if (!matches(user, password)){//错误登录失败次数+1,返回登录失败retryCount = retryCount + 1;redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);throw new UserPasswordNotMatchException();}else{//登录成功,删除错误登录次数clearLoginRecordCache(username);}}public boolean matches(SysUser user, String rawPassword){//上下文中密码,与数据库中密码做比较return SecurityUtils.matchesPassword(rawPassword, user.getPassword());}//登录成功删除之前错误登录次数public void clearLoginRecordCache(String loginName){if (redisCache.hasKey(getCacheKey(loginName))){redisCache.deleteObject(getCacheKey(loginName));}}
}

登录验证完成

创建随机uuid,赋值loginUser.token

常量+uuid作为key,将用户信息保存redis

常量+uuid 加入JWTclaims, 创建加密后的JWT token

TokenService/*** 创建令牌** @param loginUser 用户信息* @return 令牌*/public String createToken(LoginUser loginUser){//生成随机UUIDString token = IdUtils.fastUUID();loginUser.setToken(token);setUserAgent(loginUser);//常量+uuid作为key,将用户信息保存redisrefreshToken(loginUser);Map<String, Object> claims = new HashMap<>();claims.put(Constants.LOGIN_USER_KEY, token);//JWT,常量+UUID加密创建tokenreturn createToken(claims);}/*** 刷新令牌有效期,第一次就是保存** @param loginUser 登录信息*/public void refreshToken(LoginUser loginUser){loginUser.setLoginTime(System.currentTimeMillis());loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);// 根据uuid将loginUser缓存String userKey = getTokenKey(loginUser.getToken());//某个常量加uuid 作为key,将用户信息保存redisredisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);}/*** JWT从数据声明生成令牌** @param claims 数据声明* @return 令牌*/private String createToken(Map<String, Object> claims){String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();return token;}

用户请求,被JwtAuthenticationTokenFilter过滤

/*** token过滤器 验证token有效性**/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{@Autowiredprivate TokenService tokenService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException{//从请求头中获得token,解析token,获得uuid,通过uuid 获得redis缓存中loginUser对象LoginUser loginUser = tokenService.getLoginUser(request);if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())){//验证token 失效时间,不足20分钟自动刷新tokenService.verifyToken(loginUser);//创建一个新的已认证令牌UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());//设置已认证令牌的DetailsauthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));//将已认证令牌设置到安全上下文SecurityContextHolder.getContext().setAuthentication(authenticationToken);}//请求转发给过滤器链下一个filter , 如果没有filter那就是请求的资源chain.doFilter(request, response);}
}

获得请求头里面token,TokenService类

	 /*** 获取用户身份信息** @return 用户信息*/public LoginUser getLoginUser(HttpServletRequest request){// 获取请求头中tokenString token = getToken(request);if (StringUtils.isNotEmpty(token)){try{Claims claims = parseToken(token);// 获得token中的uuidString uuid = (String) claims.get(Constants.LOGIN_USER_KEY);String userKey = getTokenKey(uuid);// 从缓存中获得LoginUser对象LoginUser user = redisCache.getCacheObject(userKey);return user;}catch (Exception e){log.error("获取用户信息异常'{}'", e.getMessage());}}return null;}/*** 获取请求token** @param request* @return token*/private String getToken(HttpServletRequest request){//从请求头中获取Authorization对应的值String token = request.getHeader(header);//判断请求头中是否有Authorization键值对,并且值以Bearer开头if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)){//将Bearer替换为空串,获得真正的token值token = token.replace(Constants.TOKEN_PREFIX, "");}return token;}

退出操作,直接实现LogoutSuccessHandler ,然后加入到SecurityConfig 过滤链中

/*** 自定义退出处理类 返回成功* */
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{@Autowiredprivate TokenService tokenService;/*** 退出处理* * @return*/@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException, ServletException{LoginUser loginUser = tokenService.getLoginUser(request);if (StringUtils.isNotNull(loginUser)){String userName = loginUser.getUsername();// 删除用户缓存记录tokenService.delLoginUser(loginUser.getToken());// 记录用户退出日志AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));}ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success"))));}
}

定义权限上下文持有者,将参数放入RequestContextHolder,或者从RequestContextHolder取出,简单来说就是

属性与当前HTTP请求相关联的方法

/*** 权限信息* */
public class PermissionContextHolder
{private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT";public static void setContext(String permission){RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission,RequestAttributes.SCOPE_REQUEST);}public static String getContext(){return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES,RequestAttributes.SCOPE_REQUEST));}
}

自定义权限注解,就是判断注解上权限值,与用户权限是否一致

/**自定义权限注解* * */
@Service("ss")
public class PermissionService
{/*** 验证用户是否具备某权限** @param permission 权限字符串* @return 用户是否具备某权限*/public boolean hasPermi(String permission){if (StringUtils.isEmpty(permission)){return false;}//从上下文文中获取登录对象LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())){return false;}PermissionContextHolder.setContext(permission);//注解权限与用户权限做比较return hasPermissions(loginUser.getPermissions(), permission);}/*** 验证用户是否不具备某权限,与 hasPermi逻辑相反** @param permission 权限字符串* @return 用户是否不具备某权限*/public boolean lacksPermi(String permission){return hasPermi(permission) != true;}/*** 验证用户是否具有以下任意一个权限** @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表* @return 用户是否具有以下任意一个权限*/public boolean hasAnyPermi(String permissions){if (StringUtils.isEmpty(permissions)){return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())){return false;}PermissionContextHolder.setContext(permissions);Set<String> authorities = loginUser.getPermissions();for (String permission : permissions.split(Constants.PERMISSION_DELIMETER)){if (permission != null && hasPermissions(authorities, permission)){return true;}}return false;}/*** 判断用户是否拥有某个角色** @param role 角色字符串* @return 用户是否具备某角色*/public boolean hasRole(String role){if (StringUtils.isEmpty(role)){return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())){return false;}for (SysRole sysRole : loginUser.getUser().getRoles()){String roleKey = sysRole.getRoleKey();if (Constants.SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))){return true;}}return false;}/*** 验证用户是否不具备某角色,与 isRole逻辑相反。** @param role 角色名称* @return 用户是否不具备某角色*/public boolean lacksRole(String role){return hasRole(role) != true;}/*** 验证用户是否具有以下任意一个角色** @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表* @return 用户是否具有以下任意一个角色*/public boolean hasAnyRoles(String roles){if (StringUtils.isEmpty(roles)){return false;}//从上下文中获取LoginUserLoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())){return false;}for (String role : roles.split(Constants.ROLE_DELIMETER)){if (hasRole(role)){return true;}}return false;}/*** 判断是否包含权限** @param permissions 权限列表* @param permission 权限字符串* @return 用户是否具备某权限*/private boolean hasPermissions(Set<String> permissions, String permission){return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));}
}

自定义注解实现匿名访问

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Anonymous
{
}

注解匿名访问配置类,获得注解方法上的url

@Configuration
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
{private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");private ApplicationContext applicationContext;private List<String> urls = new ArrayList<>();public String ASTERISK = "*";@Overridepublic void afterPropertiesSet(){RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();map.keySet().forEach(info -> {HandlerMethod handlerMethod = map.get(info);// 获取方法上边的注解 替代path variable 为 *Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()).forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));// 获取类上边的注解, 替代path variable 为 *Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()).forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));});}@Overridepublic void setApplicationContext(ApplicationContext context) throws BeansException{this.applicationContext = context;}public List<String> getUrls(){return urls;}public void setUrls(List<String> urls){this.urls = urls;}
}

使用注解

@PostMapping("/url")
@Anonymous()
public AjaxResult anonymouTest(){return AjaxResult.success("操作成功");
}

SpringSecurity 配置文件

//prePostEnabled = true 时,@PreAuthorize 用于在方法执行前进行权限检查,@PostAuthorize 用于在方法执行后进行权限检查。
//securedEnabled = true 时,@Secured 用于允许具有特定角色的用户访问。
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig
{/*** 自定义用户认证逻辑*/@Autowiredprivate UserDetailsService userDetailsService;/*** 认证失败处理类*/@Autowiredprivate AuthenticationEntryPointImpl unauthorizedHandler;/*** 退出处理类*/@Autowiredprivate LogoutSuccessHandlerImpl logoutSuccessHandler;/*** token认证过滤器*/@Autowiredprivate JwtAuthenticationTokenFilter authenticationTokenFilter;/*** 跨域过滤器*/@Autowiredprivate CorsFilter corsFilter;/*** 允许匿名访问的地址*/@Autowiredprivate PermitAllUrlProperties permitAllUrl;/*** 身份验证实现*/@Beanpublic AuthenticationManager authenticationManager(){DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();daoAuthenticationProvider.setUserDetailsService(userDetailsService);daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());return new ProviderManager(daoAuthenticationProvider);}/*** anyRequest          |   匹配所有请求路径* access              |   SpringEl表达式结果为true时可以访问* anonymous           |   匿名可以访问* denyAll             |   用户不能访问* fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)* hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问* hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问* hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问* hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问* hasRole             |   如果有参数,参数表示角色,则其角色可以访问* permitAll           |   用户可以任意访问* rememberMe          |   允许通过remember-me登录的用户访问* authenticated       |   用户登录后可访问*/@Beanprotected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception{return httpSecurity// CSRF禁用,因为不使用session.csrf(csrf -> csrf.disable())// 禁用HTTP响应标头.headers((headersCustomizer) -> {headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());})// 认证失败处理类.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))// 基于token,所以不需要session.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))// 注解标记允许匿名访问的url.authorizeHttpRequests((requests) -> {permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());// 对于登录login 注册register 验证码captchaImage 允许匿名访问requests.antMatchers("/login", "/register", "/captchaImage").permitAll()// 静态资源,可匿名访问.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll().antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();})// 添加Logout filter.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))// 添加JWT filter.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)// 添加CORS filter.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class).addFilterBefore(corsFilter, LogoutFilter.class).build();}/*** 强散列哈希加密实现*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}
}

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

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

相关文章

阿里巴巴即将超越OpenAI的o1?

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

第29天 MCU入门

目录 MCU介绍 MCU的组成与作用 电子产品项目开发流程 硬件开发流程 常用元器件初步了解 硬件原理图与PCB板 常见电源符号和名称 电阻 电阻的分类 贴片电阻的封装说明&#xff1a; 色环电阻的计算 贴片电阻阻值计算 上拉电阻与下拉电阻 电容 电容的读数 二极管 LED 灯电路 钳位作…

Pump Science平台深度剖析:兴起、优势、影响与未来

在过去的几个月里&#xff0c;人们越来越关注去中心化科学&#xff08;DeSci&#xff09;。DeSci 是一种利用区块链技术进行科学研究的新方法。传统的科学研究经常面临所谓的“死亡之谷”&#xff0c;这指的是基础科学研究与成功开发和造福患者的实施之间的重要时期。DeSci 旨在…

Tülu 3:重新定义开源大模型的后训练范式

一、引言 在大型语言模型&#xff08;LLM&#xff09;的发展历程中&#xff0c;预训练阶段往往受到最多关注&#xff0c;动辄需要数百万美元算力投入和数万亿token的训练数据。然而&#xff0c;一个鲜为人知但同样关键的事实是&#xff1a;预训练完成的模型实际上并不能直接投…

Excel中根据某列内容拆分为工作簿

简介&#xff1a;根据A列的内容进行筛选&#xff0c;将筛选出来的数据生成一个新的工作簿(可以放到指定文件夹下)&#xff0c;且工作簿名为筛选内容。 举例&#xff1a; 将上面的内容使用VBA会在当前test1下生成5个工作簿&#xff0c;工作簿名分别为TEST1.xls TEST2.xls TEST3…

【电子通识】“标准的标准”:国家标准GB/T 1.1-2020 标准的分类

标准是可以分类的。比如根据层次、专业、对象、目的、功能等维度进行分类。 以层次进行分类&#xff1a; 可以分为国际标准、区域标准、国家标准、团体标准、企业标准、行业标准 以专业进行分类&#xff1a; 可以分为农业、医药、卫生、劳动保护、矿业、化工、机械等等。 以对象…

【网络安全设备系列】12、态势感知

0x00 定义&#xff1a; 态势感知&#xff08;Situation Awareness&#xff0c;SA&#xff09;能够检测出超过20大类的云上安全风险&#xff0c;包括DDoS攻击、暴力破解、Web攻击、后门木马、僵尸主机、异常行为、漏洞攻击、命令与控制等。利用大数据分析技术&#xff0c;态势感…

数据库(MySQL黑马)

基础篇 MySQL概述 数据库概述 数据库相关概念 主流的关系型数据库管理系统 MySQL数据库的安装与启动 下载&#xff1a;MySQL :: MySQL Community Downloads 安装步骤 MySQL―8.0.40超详细保姆级安装教程_mysql8.0.40安装教程-CSDN博客文章浏览阅读1k次。_mysql8.0.40安装教…

Java算法OJ(11)双指针练习

目录 1.前言 2.正文 2.1存在重复数字 2.1.1题目 2.1.2解法一代码 解析&#xff1a; 2.1.3解法二代码 解析&#xff1a; 2.2存在重复数字plus 2.2.1题目 2.2.2代码 2.2.3解析 3.小结 1.前言 哈喽大家好吖&#xff0c;今天来给大家分享双指针算法的相关练习&…

Maven、JAVAWeb、Servlet

知识点目标 1、MavenMaven是什么Maven项目的目录结构Maven的Pom文件Maven的命令Maven依赖管理Maven仓库JavaWeb项目 2.网络基础知识 3、ServletMaven Maven是什么 Maven是Java的项目管理工具&#xff0c;可以构建&#xff0c;打包&#xff0c;部署项目&#xff0c;还可以管理…

python | 6 个在 cmd 中常用的 python 命令

本文涉及的 python 常见使用命令示例 均是在默认已经配置了 python 环境变量的情况下 如果安装 python 环境时没有配置环境变量&#xff0c;需要先进入到安装 python 的文件路径下&#xff0c;否则会提示命令不存在。其原理及配置方法可参考往期文章&#xff1a; 环境变量是…

CondaValueError: Malformed version string ‘~‘: invalid character(s).

问题描述&#xff1a;在window下使用conda安装任何包都会报错。报错信息是CondaValueError: Malformed version string ~: invalid character(s). 解决办法&#xff1a;把.condarc文件的源地址删除&#xff08;八成是源地址访问不了了&#xff09;&#xff0c;只保存默认的&am…

Zookeeper3.5.8集群部署

环境说明 准备三台服务器&#xff0c;我这边是虚拟机&#xff0c;分别为&#xff1a;bigdata141、bigdata142、bigdata143 下载安装包 下载链接&#xff1a;Index of /dist/zookeeper/zookeeper-3.5.8 下载完后&#xff0c;上传到其中一台服务器&#xff0c;我这边上传到 b…

JVM_总结详解

1、CPU和内存的交互 了解jvm内存模型前&#xff0c;了解下cpu和计算机内存的交互情况。【因为Java虚拟机内存模型定义的访问操作与计算机十分相似】 有篇很棒的文章&#xff0c;从cpu讲到内存模型:[什么是java内存模型&#xff1f;] 在计算机中&#xff0c;cpu和内存的交互最…

屏幕分辨率|尺寸|颜色深度指纹修改

一、前端通过window.screen接口获取屏幕分辨率 尺寸 颜色深度&#xff0c;横屏竖屏信息。 二、window.screen c接口实现&#xff1a; 1、third_party\blink\renderer\core\frame\screen.idl // https://drafts.csswg.org/cssom-view/#the-screen-interface[ExposedWindow ] …

vue3实现自定义导航菜单

一、创建项目 1. 打开HBuilder X 图1 2. 新建一个空项目 文件->新建->项目->uni-app 填写项目名称&#xff1a;vue3demo 选择项目存放目录&#xff1a;D:/HBuilderProjects 一定要注意vue的版本&#xff0c;当前选择的版本为vue3 图2 点击“创建”之后进入项目界面 图…

BASLER工业相机维修不能触发拍照如何处理解决这个问题

BASLER工业相机维修不能触发拍照如何处理解决这个问题&#xff1f;最近遇到挺多工业相机维修咨询这个不能触发拍照的案例&#xff0c;所以今天优米佳维修的技术就抽空整理了这篇关于BASLER相机不能触发拍照的处理方法分享给大家。 当碰到巴斯勒工业相机不能触发拍照的问题&…

深入理解计算机系统,源码到可执行文件翻译过程:预处理、编译,汇编和链接

1.前言 从一个高级语言到可执行程序&#xff0c;要经过预处理、编译&#xff0c;汇编和链接四个过程。大家可以思考下&#xff0c;为什么要有这样的过程&#xff1f; 我们学习计算机之处&#xff0c;就应该了解到&#xff0c;计算机能够识别的只有二进制语言&#xff08;这是…

HTTP代理是什么,主要用来干嘛?

在探讨互联网通信和数据传输的广阔领域中&#xff0c;HTTP代理作为一个重要而广泛使用的工具&#xff0c;扮演着不可或缺的角色。本文将深入浅出地介绍HTTP代理的基本概念、工作原理及其主要应用场景。 一、HTTP代理的基本概念 HTTP代理&#xff0c;简而言之&#xff0c;是一…

“不可能三角”的“宿敌”,AI辅助技术撕开“第一道口子”

尽管&#xff0c;打破看得好病、看得起病和看得上病这个“不可能三角”&#xff0c;无法一蹴而就&#xff0c;但是在人工智能浪潮的推动下&#xff0c;“不可能”也在逐渐向“可能”转变。 近日&#xff0c;国家医保局在相关新闻发布会上表示&#xff0c;目前已编制发布17批立…