用户登录:断点看流程认证
之前我们知道
java">生成令牌之后,我们就调用全局保存令牌
SecurityContextHolder.getContext().setAuthentication(authentication);
SecurityContextHolder.getContext()获取的是SecurityContext就是设置令牌和保存令牌
java">/** Copyright 2004, 2005, 2006 Acegi Technology Pty Limited** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.security.core.context;import java.io.Serializable;import org.springframework.security.core.Authentication;/*** Interface defining the minimum security information associated with the current thread* of execution.** <p>* The security context is stored in a {@link SecurityContextHolder}.* </p>** @author Ben Alex*/
public interface SecurityContext extends Serializable {/*** Obtains the currently authenticated principal, or an authentication request token.* @return the <code>Authentication</code> or <code>null</code> if no authentication* information is available*/Authentication getAuthentication();/*** Changes the currently authenticated principal, or removes the authentication* information.* @param authentication the new <code>Authentication</code> token, or* <code>null</code> if no further authentication information should be stored*/void setAuthentication(Authentication authentication);}
之前的令牌我们存的是
public class JwtUserDto implements UserDetails
里面又三个属性
private final User user;private final List<Long> dataScopes;private final List<AuthorityDto> authorities;
但是UserDetails接口并没有这三个属性的获取方法而存入令牌的就是UserDetails类
看一下UserDetails接口(里面没有我们三个属性的方法)
java">/** Copyright 2004, 2005, 2006 Acegi Technology Pty Limited** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.security.core.userdetails;import java.io.Serializable;
import java.util.Collection;import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;/*** Provides core user information.** <p>* Implementations are not used directly by Spring Security for security purposes. They* simply store user information which is later encapsulated into {@link Authentication}* objects. This allows non-security related user information (such as email addresses,* telephone numbers etc) to be stored in a convenient location.* <p>* Concrete implementations must take particular care to ensure the non-null contract* detailed for each method is enforced. See* {@link org.springframework.security.core.userdetails.User} for a reference* implementation (which you might like to extend or use in your code).** @author Ben Alex* @see UserDetailsService* @see UserCache*/
public interface UserDetails extends Serializable {/*** Returns the authorities granted to the user. Cannot return <code>null</code>.* @return the authorities, sorted by natural key (never <code>null</code>)*/Collection<? extends GrantedAuthority> getAuthorities();/*** Returns the password used to authenticate the user.* @return the password*/String getPassword();/*** Returns the username used to authenticate the user. Cannot return* <code>null</code>.* @return the username (never <code>null</code>)*/String getUsername();/*** Indicates whether the user's account has expired. An expired account cannot be* authenticated.* @return <code>true</code> if the user's account is valid (ie non-expired),* <code>false</code> if no longer valid (ie expired)*/boolean isAccountNonExpired();/*** Indicates whether the user is locked or unlocked. A locked user cannot be* authenticated.* @return <code>true</code> if the user is not locked, <code>false</code> otherwise*/boolean isAccountNonLocked();/*** Indicates whether the user's credentials (password) has expired. Expired* credentials prevent authentication.* @return <code>true</code> if the user's credentials are valid (ie non-expired),* <code>false</code> if no longer valid (ie expired)*/boolean isCredentialsNonExpired();/*** Indicates whether the user is enabled or disabled. A disabled user cannot be* authenticated.* @return <code>true</code> if the user is enabled, <code>false</code> otherwise*/boolean isEnabled();}
要定义一个工具类SecurityUtils获取令牌里的值
问题:获取用户属性里面数据怎么获取?
看类SecurityUtils
getCurrentUser方法(所以贴了SpringContextHolder类):
在获取登录用户用到类SpringContextHolder去找UserDetailsService
主要看getCurrentUsername这个方法里调用
SecurityContextHolder.getContext().getAuthentication()获取令牌后获取usernanme(这就是为啥一开始SecurityContextHolder.getContext().setAuthentication(authentication);这代码的用处)
答案就是用json(工具类下这几个方法)
getCurrentUserRole
getCurrentUserId
getCurrentUserDataScope
java">package com.njry.utils;import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.njry.exception.BadRequestException;
import com.njry.utils.enums.DataScopeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;/*** 获取当前登录的用户* @author * @date 2024-05-11*/
@Slf4j
public class SecurityUtils {/*** 获取当前登录的用户* @return UserDetails*/public static UserDetails getCurrentUser() {UserDetailsService userDetailsService = SpringContextHolder.getBean(UserDetailsService.class);return userDetailsService.loadUserByUsername(getCurrentUsername());}/*** 获取系统用户名称** @return 系统用户名称*/public static String getCurrentUsername() {final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication == null) {throw new BadRequestException(HttpStatus.UNAUTHORIZED, "当前登录状态过期");}
//这里因为令牌是UserDetails,所以可以调用getUsername,且我们public class JwtUserDto implements UserDetails里面重写了getUsername方法,就是user的getUsername,但是没有getUser方法可以调用,导致获取user里面一些别的数据很难if (authentication.getPrincipal() instanceof UserDetails) {UserDetails userDetails = (UserDetails) authentication.getPrincipal();System.out.println("登陸報錯的系統用戶名"+userDetails);return userDetails.getUsername();}throw new BadRequestException(HttpStatus.UNAUTHORIZED, "找不到当前登录的信息");}/*** 获取系统用户ID* @return 系统用户ID*/public static Long getCurrentUserId() {UserDetails userDetails = getCurrentUser();// 将 Java 对象转换为 JSONObject 对象JSONObject jsonObject = (JSONObject) JSON.toJSON(userDetails);return jsonObject.getJSONObject("user").getLong("id");}/*** 获取当前用户的数据权限* @return /*/public static List<Long> getCurrentUserDataScope(){UserDetails userDetails = getCurrentUser();// 将 Java 对象转换为 JSONObject 对象JSONObject jsonObject = (JSONObject) JSON.toJSON(userDetails);JSONArray jsonArray = jsonObject.getJSONArray("dataScopes");return JSON.parseArray(jsonArray.toJSONString(), Long.class);}/*** 获取当前用户的角色* @return /*/public static List<String> getCurrentUserRole(){UserDetails userDetails = getCurrentUser();// 将 Java 对象转换为 JSONObject 对象JSONObject jsonObject = (JSONObject) JSON.toJSON(userDetails);JSONObject jsonObjectUser = jsonObject.getJSONObject("user");JSONArray roles = jsonObjectUser.getJSONArray("roles");
// return JSON.parseArray(roles.toJSONString(), Role.class);return JSON.parseArray(roles.toJSONString(), String.class);}/*** 获取数据权限级别* @return 级别*/public static String getDataScopeType() {List<Long> dataScopes = getCurrentUserDataScope();if(dataScopes.size() != 0){return "";}return DataScopeEnum.ALL.getValue();}
}
类SpringContextHolder
java">package com.njry.utils;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** @author * @date 2019-01-07*/
@Slf4j
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {private static ApplicationContext applicationContext = null;private static final List<CallBack> CALL_BACKS = new ArrayList<>();private static boolean addCallback = true;/*** 针对 某些初始化方法,在SpringContextHolder 未初始化时 提交回调方法。* 在SpringContextHolder 初始化后,进行回调使用** @param callBack 回调函数*/public synchronized static void addCallBacks(CallBack callBack) {if (addCallback) {SpringContextHolder.CALL_BACKS.add(callBack);} else {log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName());callBack.executor();}}/*** 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.*/@SuppressWarnings("unchecked")public static <T> T getBean(String name) {assertContextInjected();return (T) applicationContext.getBean(name);}/*** 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.*/public static <T> T getBean(Class<T> requiredType) {assertContextInjected();return applicationContext.getBean(requiredType);}/*** 获取SpringBoot 配置信息** @param property 属性key* @param defaultValue 默认值* @param requiredType 返回类型* @return /*/public static <T> T getProperties(String property, T defaultValue, Class<T> requiredType) {T result = defaultValue;try {result = getBean(Environment.class).getProperty(property, requiredType);} catch (Exception ignored) {}return result;}/*** 获取SpringBoot 配置信息** @param property 属性key* @return /*/public static String getProperties(String property) {return getProperties(property, null, String.class);}/*** 获取SpringBoot 配置信息** @param property 属性key* @param requiredType 返回类型* @return /*/public static <T> T getProperties(String property, Class<T> requiredType) {return getProperties(property, null, requiredType);}/*** 检查ApplicationContext不为空.*/private static void assertContextInjected() {if (applicationContext == null) {throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" +".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");}}/*** 清除SpringContextHolder中的ApplicationContext为Null.*/private static void clearHolder() {log.debug("清除SpringContextHolder中的ApplicationContext:"+ applicationContext);applicationContext = null;}@Overridepublic void destroy() {SpringContextHolder.clearHolder();}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {if (SpringContextHolder.applicationContext != null) {log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);}SpringContextHolder.applicationContext = applicationContext;if (addCallback) {for (CallBack callBack : SpringContextHolder.CALL_BACKS) {callBack.executor();}CALL_BACKS.clear();}SpringContextHolder.addCallback = false;}/*** 获取 @Service 的所有 bean 名称* @return /*/public static List<String> getAllServiceBeanName() {return new ArrayList<>(Arrays.asList(applicationContext.getBeanNamesForAnnotation(Service.class)));}
}
项目中实际使用(比如下面两个)
在controller层经常要校验权限
java"> @GetMapping(value = "/detail")@Log("查询原子定义管理详情")@ApiOperation("查询原子定义管理详情")@PreAuthorize("@el.check('atomDict:list')")public ResponseEntity<AtomDict> queryAtomDictDetail(AtomDictQueryCriteria criteria){return new ResponseEntity<>(atomDictService.queryAtomDictDetail(criteria),HttpStatus.OK);}@GetMapping@Log("查询原子定义管理")@ApiOperation("查询原子定义管理")
// @PreAuthorize("@el.check('atomDict:list')")@PreAuthorize("@el.checkRole()")public ResponseEntity<PageResult<AtomDict>> queryAtomDict(AtomDictQueryCriteria criteria, Page<Object> page){return new ResponseEntity<>(atomDictService.queryAll(criteria,page),HttpStatus.OK);}
类AuthorityConfig 校验权限和角色
java">package com.njry.config;import com.njry.utils.SecurityUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;/*** @author wj*/
@Service(value = "el")
public class AuthorityConfig {public Boolean check(String ...permissions){// 获取当前用户的所有权限List<String> elPermissions = SecurityUtils.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());System.out.println("当前用户的权限"+elPermissions);// 判断当前用户的所有权限是否包含接口上定义的权限return elPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(elPermissions::contains);}public Boolean checkRole(){// 获取当前用户的所有角色List<String> currentUserRole = SecurityUtils.getCurrentUserRole();String sbString = "400";//数据库一个角色的idList<String> collect = currentUserRole.stream().filter(c -> c.contains(sbString)).collect(Collectors.toList());
// UserDetails里面没有获取用户user 的getter方法
// 虽然 class JwtUserDto implements UserDetailsif(collect.size() > 0){return true;}else{// 判断当前用户的所有权限是否包含接口上定义的权限return false;}}
}