在系统开发中,安全是十分重要必不可少的一环,如果没有安全框架,用户信息随时都可能被泄露,篡改等风险,系统将处于十分不可信的状态。为此我们将介绍权限框架之spring security的原理与使用。
Spring Security vs Apache Shiro
Apache Shiro 诞生于 2004 年,当时名为 JSecurity,并于 2008 年被 Apache 基金会接受,Spring Security 于 2003 年作为 Acegi 开始,并在 2008 年首次公开发布时并入 Spring Framework。两种技术都提供身份验证和授权支持以及加密和会话管理解决方案。但是在如今的微服务开发中Shiro 几乎处在被弃用的边缘,那为什么要选择Spring Security呢?
- Spring Security是spring Framework下的安全解决方案,社区友好且强大。
- 不仅提供权限控制等功能还提供一流的保护,以抵御 CSRF 和会话固定等攻击,功能强大。
- Springboot/Cloud微服务项目中配置简单,十分方便。
- 业务代码与权限分离,通过切面的形式提供权限控制,不侵入主干代码。
Spring Security简介
主要功能是提供认证和授权服务。
认证
验证用户身份,如账号密码验证,短信验证等。
授权
确定是否有此资源的访问权限。
认证过程
基于过滤器链完成登录认证与授权的功能,如果认证授权过程中发现用户不合法或者无权限访问资源,则会交由失败过滤器处理。
注:这里 Security 提供两种过滤器类:UsernamePasswordAuthenticationFilter 表示表单登陆过滤器 BasicAuthenticationFilter 表示 httpBasic 方式登陆过滤器
如上图,一个请求想要访问到 API 就会以从左到右的形式经过蓝线框框里面的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分负责异常处理,橙色部分则是负责授权。
上述的两个过滤器是 Spring Security 对 form 表单认证和 Basic 认证内置的两个 Filter,有两个自带的叫 formLogin 和 httpBasic 的配置项,而 JWT 认证方式用不上。
java
@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().and().formLogin() // formLogin 对应着 form 表单认证方式,即 UsernamePasswordAuthenticationFilter .and().httpBasic(); // httpBasic 对应着 Basic 认证方式,即 BasicAuthenticationFilter}
换言之,配置了这两种认证方式,过滤器链中才会加入它们,否则它们是不会被加到过滤器链中去的。
因为 Spring Security 自带的过滤器中是没有针对 JWT 的认证方式,所以需要自己写一个 JWT 的认证过滤器,然后放在绿色的位置进行认证工作。下面将介绍JWT认证来了解Security认证过程。
认证组件
- SecurityContext:上下文对象,Authentication 对象会放在里面。
- SecurityContextHolder:用于拿到上下文对象的静态工具类。
- Authentication:认证接口,定义了认证对象的数据形式。
- AuthenticationManager:用于校验 Authentication,返回一个认证完成后的 Authentication 对象。
SecurityContext
上下文对象,认证后的数据就放在这里面,接口定义如下:
java
public interface SecurityContext extends Serializable {// 获取Authentication对象 Authentication getAuthentication();// 放入Authentication对象 void setAuthentication(Authentication authentication);
}
这个接口里面只有两个方法,其主要作用就是 get or set Authentication
SecurityContextHolder
用于拿到上下文对象的静态工具类。
SecurityContextHolder 用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限... 这些都被保存在 SecurityContextHolder 中。SecurityContextHolder 默认使用 ThreadLocal 策略来存储认证信息。
java
public class SecurityContextHolder {
public static void clearContext() {
strategy.clearContext();
}
public static SecurityContext getContext() {
return strategy.getContext();
}
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
}
可以说是 SecurityContext 的工具类,用于 get or set or clear SecurityContext,默认会把数据都存储到当前线程中。
从这个 SecurityContextHolder 取得 UserDetail 的实例:
plaintext
public static String getLoginAccount() {// getPrincipal 返回值需要强转为 UserDetail 具体原因看下面的 Authentication那节 return ((UserDetail) SecurityContextHolder.getContext().getAuthentication() // 返回:Authentication .getPrincipal()) // 这里就是 Authentication 内部保存的 UserDetail 对象 .getUsername();
}
Authentication
认证接口,定义了认证对象的数据形式。
java
public interface Authentication extends Principal, Serializable {Collection<? extends GrantedAuthority> getAuthorities();Object getCredentials();Object getDetails();// AuthenticationManager 实现通常会返回一个包含更丰富信息的 Authentication 作为供应用程序使用的主体。 // 许多身份验证提供程序将创建一个 UserDetails 对象作为主体// 所以如果 AuthenticationManager 使用的是 ProviderManager 则这里返回值需要强转为 UserDetails Object getPrincipal();boolean isAuthenticated();void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
这几个方法效果如下:
- getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。
- getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。
- getDetails: 获取用户的额外信息,(这部分信息可以是我们的用户表中的信息)。
- getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails。
- isAuthenticated: 获取当前 Authentication 是否已认证。
- setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。
Authentication 只是定义了一种在 SpringSecurity 进行认证过的数据的数据形式应该是怎么样的,要有权限,要有密码,要有身份信息,要有额外信息。
AuthenticationManager
java
public interface AuthenticationManager {// 认证方法 Authentication authenticate(Authentication authentication)throws AuthenticationException;
}
AuthenticationManager (接口)是认证相关的核心接口,它定义了一个认证方法,它将一个未认证的 Authentication 传入,返回一个已认证的 Authentication。它的默认实现类是 ProviderManager
注:AuthenticationManager 有多个认证类 AuthenticationManager,ProviderManager ,AuthenticationProvider ...
所以整体的认证流程为:
- 接收用户请求后,由UsernamePasswordAuthenticationFilter(表单形式) 、BasicAuthenticationFilter(BasicHttp形式)、JWT形式自定义过滤器处理封装成Authentication(通常是Authentication的子类UsernamePasswordAuthenticationToken)
- 这个 Authentication 经过 AuthenticationManager 的认证(身份管理器负责验证)认证成功后,AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication 实例。
- SecurityContextHolder 安全上下文容器将上面填充了信息的 Authentication,通过 SecurityContextHolder.getContext().setAuthentication(...) 方法,设置到 SecurityContext 其中。
看到这里大家肯定有个困惑AuthenticationManager是如何进行认证的呢,稍等片刻,小二这就帮你解答,上代码。
AuthenticationManager认证
ProviderManager
是AuthenticationManager的默认实现类,对认证方法进行了具体的实现,后续的处理(获取用户信息,密码校验)等将围绕着次方法进行。
java
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
catch (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
throw e;
}
catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = e;
}
}
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
AuthenticationProvider
接口认证(AbstractUserDetailsAuthenticationProvider默认实现)提供了具体的认证逻辑(获取用户信息,密码加密匹配)。
java
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
}
Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
}
try {
// 校验用户状态(停用、被锁、过期等)
preAuthenticationChecks.check(user);
// 校验密码是否正确
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
}
// 校验用户状态(停用、被锁、过期等)
postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
DaoAuthenticationProvider
定义了获取用户信息,密码匹配等方法。
java
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
// loadUserByUsername是一个抽象方法需要自己实现
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
loadUserByUsername
是一个抽象方法需要自己实现
java
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private static final Logger log= LoggerFactory.getLogger(UserDetailsServiceImpl.class);
private final UserV2Service userV2Service;
private final PermissionV2Service permissionService;
private final GameGroupService gameGroupService;
public UserDetailsServiceImpl(UserV2Service userV2Service, PermissionV2Service permissionService, GameGroupService gameGroupService) {
this.userV2Service = userV2Service;
this.permissionService = permissionService;
this.gameGroupService = gameGroupService;
}
@Override
public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
UserV2 user = userV2Service.getUserByAccount(account);
if (user == null) {
log.info("登录账号:{} 用户名或密码错误.", account);
throw new BussinessException("登录账号:" + account + " 用户名或密码错误");
} else if (UserV2.STATUS_DISABLE == user.getStatus()) {
log.info("登录账号:{} 已被停用.", account);
throw new BussinessException("对不起,您的账号:" + account + " 已停用,请联系管理员");
}
return createLoginUser(user);
}
public UserDetails createLoginUser(UserV2 user) {
List<String> gameCodes = new Gson().fromJson(user.getGameCodes().toJSONString(), new TypeToken<ArrayList<String>>() {
}.getType());
List<String> mediaCodes = new Gson().fromJson(user.getMediaCodes().toJSONString(), new TypeToken<ArrayList<String>>() {
}.getType());
return new LoginUser(user.getId(), user, permissionService.getPermissionV2VoList(user.getId()), gameCodes, mediaCodes);
}
}
additionalAuthenticationChecks
校验密码是否正确DaoAuthenticationProvider(AbstractUserDetailsAuthenticationProvider的实现类)提供了PasswordEncoder加密匹配密码判断。
java
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
至此AuthenticationManager认证完成,返回填充了用户数据的认证信息,并将此数据保存到上下文中。AnonymousAuthenticationFilter将认证通过的Authentication 设置到上下文中。
java
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(
createAuthentication((HttpServletRequest) req));
if (logger.isDebugEnabled()) {
logger.debug("Populated SecurityContextHolder with anonymous token: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
chain.doFilter(req, res);
}
整合实战
引入依赖
java
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
定义JWT过滤器
java
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private final TokenService tokenService;
public JwtAuthenticationTokenFilter(TokenService tokenService) {
this.tokenService = tokenService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
LoginUser loginUser = tokenService.getLoginUser(request);
if (loginUser != null && SecurityUtils.getAuthentication() == null) {
tokenService.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
chain.doFilter(request, response);
}
}
定义用户自定义逻辑类UserDetailsService
java
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private static final Logger log= LoggerFactory.getLogger(UserDetailsServiceImpl.class);
private final UserV2Service userV2Service;
private final PermissionV2Service permissionService;
private final GameGroupService gameGroupService;
public UserDetailsServiceImpl(UserV2Service userV2Service, PermissionV2Service permissionService, GameGroupService gameGroupService) {
this.userV2Service = userV2Service;
this.permissionService = permissionService;
this.gameGroupService = gameGroupService;
}
@Override
public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
UserV2 user = userV2Service.getUserByAccount(account);
if (user == null) {
log.info("登录账号:{} 用户名或密码错误.", account);
throw new BussinessException("登录账号:" + account + " 用户名或密码错误");
} else if (UserV2.STATUS_DISABLE == user.getStatus()) {
log.info("登录账号:{} 已被停用.", account);
throw new BussinessException("对不起,您的账号:" + account + " 已停用,请联系管理员");
}
return createLoginUser(user);
}
public UserDetails createLoginUser(UserV2 user) {
List<String> gameCodes = new Gson().fromJson(user.getGameCodes().toJSONString(), new TypeToken<ArrayList<String>>() {
}.getType());
List<String> mediaCodes = new Gson().fromJson(user.getMediaCodes().toJSONString(), new TypeToken<ArrayList<String>>() {
}.getType());
return new LoginUser(user.getId(), user, permissionService.getPermissionV2VoList(user.getId()), gameCodes, mediaCodes);
}
}
定义认证失败处理类AuthenticationEntryPointImpl
java
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID= -8970718410437077606L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException {
String msg = String.format("请求访问:%s,认证失败,无法访问系统资源", request.getRequestURI());
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(new Gson().toJson(BaseResponse.fail(HttpStatusConstants.UNAUTHORIZED, msg)));
}
}
定义退出登录处理类LogoutSuccessHandlerImpl
java
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
private final TokenService tokenService;
public LogoutSuccessHandlerImpl(TokenService tokenService) {
this.tokenService = tokenService;
}
/** * 退出处理 * * @return*/@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException {
LoginUser loginUser = tokenService.getLoginUser(request);
if (loginUser != null) {
// 删除用户缓存记录
tokenService.delLoginUser(loginUser.getUuid());
}
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(new Gson().toJson(BaseResponse.successfulWithMessage("退出成功")));
}
}
配置SecurityConfig
java
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/** * 自定义用户认证逻辑 */private final UserDetailsService userDetailsService;
/** * 认证失败处理类 */private final AuthenticationEntryPointImpl unauthorizedHandler;
/** * 退出处理类 */private final LogoutSuccessHandlerImpl logoutSuccessHandler;
/** * token认证过滤器 */private final JwtAuthenticationTokenFilter authenticationTokenFilter;
/** * 跨域过滤器 */private final CorsFilter corsFilter;
private final UserV2Service userV2Service;
private final RedisTemplate redisTemplate;
public SecurityConfig(UserDetailsService userDetailsService, AuthenticationEntryPointImpl unauthorizedHandler,
LogoutSuccessHandlerImpl logoutSuccessHandler, JwtAuthenticationTokenFilter authenticationTokenFilter,
CorsFilter corsFilter, UserV2Service userV2Service, RedisTemplate redisTemplate) {
this.userDetailsService = userDetailsService;
this.unauthorizedHandler = unauthorizedHandler;
this.logoutSuccessHandler = logoutSuccessHandler;
this.authenticationTokenFilter = authenticationTokenFilter;
this.corsFilter = corsFilter;
this.userV2Service = userV2Service;
this.redisTemplate = redisTemplate;
}
/** * 解决 无法直接注入 AuthenticationManager * * @return* @throwsException */@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/** * anyRequest | 匹配所有请求路径 * access | SpringEl表达式结果为true时可以访问 * anonymous | 匿名可以访问 * denyAll | 用户不能访问 * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 * hasRole | 如果有参数,参数表示角色,则其角色可以访问 * permitAll | 用户可以任意访问 * rememberMe | 允许通过remember-me登录的用户访问 * authenticated | 用户登录后可访问 */@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// CSRF禁用,因为不使用session
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/login_v2/login").anonymous()
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/test/**").permitAll()
.antMatchers("/om/**").permitAll()
.antMatchers("/ver_record/**").permitAll()
.antMatchers("/ad/db_query").permitAll()
.antMatchers("/api/**").permitAll()
.antMatchers("/authCode").permitAll()
.antMatchers("/asa/**").permitAll()
.antMatchers("/ad/excel/template", "/ad/media_account_info/template",
"/ad/material/tag/download/template", "/ad/material/config/download/template",
"/ad/finance/advert/consume/excel/template", "/ad/tool/schedule_task/template",
"/ad/jrtt/program/word/export").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
httpSecurity.authenticationProvider(new CustomDaoAuthenticationProvider(userDetailsService, userV2Service, redisTemplate));
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
}
/** * 身份认证接口 */@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
认证
提供登录服务
java
/** * 登录 * * @paramaccount 账号 * @parampwd 密码 * @return*/
public String login(String account, String pwd) {
// 用户验证
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(account, pwd));
// 清空登录失败计数
redisTemplate.opsForValue().set(USER_LOGIN_ERROR_KEY + account, 0);
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUser());
// 生成token
return tokenService.createToken(loginUser);
}
授权
定义授权控制类
java
@Service("auth")
public class AuthorizeService {
private final UserV2Mapper userV2Mapper;
private final UserDataPermissionMapper userDataPermissionMapper;
public AuthorizeService(UserV2Mapper userV2Mapper, UserDataPermissionMapper userDataPermissionMapper) {
this.userV2Mapper = userV2Mapper;
this.userDataPermissionMapper = userDataPermissionMapper;
}
/** * 操作权限校验 * * @parampermission * @paramdataOwner * @return*/public boolean hasOperationAuthority(Integer permissionId, int businessId, String mapperBeanName) {
if (permissionId == null) {
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
return false;
}
// 超级管理员直接放行
if (loginUser.getUser().getSuperAdmin() == 1) {
return true;
}
Object bean = SpringUtil.getBean(mapperBeanName);
if (bean instanceof ExtendMapper) {
ExtendMapper mapper = (ExtendMapper) bean;
Object o = mapper.selectByPrimaryKey(businessId);
Integer operator = (Integer) ReflectionUtil.getFieldValue(o, "operator");
//查询本人的操作权限范围
Integer operationAuthority = getOperationAuthority(loginUser.getPermissionV2VoList(), permissionId);
switch (operationAuthority) {
case 1:
return loginUser.getUserId().equals(operator);
case 2:
// 获取部门的人员
Example example = new Example(UserV2.class);
example.createCriteria().andEqualTo("departmentId", loginUser.getUser().getDepartmentId());
List<Integer> userIds = Optional.of(userV2Mapper.selectByExample(example).stream().map(BaseEntity::getId).collect(Collectors.toList())).orElse(new ArrayList<>(16));
return userIds.contains(operator);
case 3:
// 公司可见,直接返回true
return true;
default:
return false;
}
}
return false;
}
/** * 操作权限校验 * * @parampermission * @paramdataOwner * @return*/public boolean hasOperationAuthorityAndAgent(Integer permissionId, int businessId, String mapperBeanName, String tableName) {
if (permissionId == null) {
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
return false;
}
// 超级管理员直接放行
if (loginUser.getUser().getSuperAdmin() == 1) {
return true;
}
Object bean = SpringUtil.getBean(mapperBeanName);
if (bean instanceof ExtendMapper) {
Example userDataPermissionExample = new Example(UserDataPermission.class);
userDataPermissionExample.createCriteria().andEqualTo("status", 1).andEqualTo("tableName", tableName).andEqualTo("userId", loginUser.getUserId());
List<UserDataPermission> userDataPermissions = Optional.ofNullable(userDataPermissionMapper.selectByExample(userDataPermissionExample)).orElse(new ArrayList<>(16));
List<Integer> relatedIds = userDataPermissions.stream().map(UserDataPermission::getRelatedId).collect(Collectors.toList());
ExtendMapper mapper = (ExtendMapper) bean;
Object o = mapper.selectByPrimaryKey(businessId);
Integer operator = (Integer) ReflectionUtil.getFieldValue(o, "operator");
//查询本人的操作权限范围
Integer operationAuthority = getOperationAuthority(loginUser.getPermissionV2VoList(), permissionId);
switch (operationAuthority) {
case 1:
return loginUser.getUserId().equals(operator) || relatedIds.contains(businessId);
case 2:
// 获取部门的人员
Example example = new Example(UserV2.class);
example.createCriteria().andEqualTo("departmentId", loginUser.getUser().getDepartmentId());
List<Integer> userIds = Optional.of(userV2Mapper.selectByExample(example).stream().map(BaseEntity::getId).collect(Collectors.toList())).orElse(new ArrayList<>(16));
return userIds.contains(operator) || relatedIds.contains(businessId);
case 3:
// 公司可见,直接返回true
return true;
default:
return false;
}
}
return false;
}
/** * 操作权限校验 * * @parampermission * @paramdataOwner * @return*/public boolean hasOperationAuthorityAndAgent(Integer permissionId, List<Integer> businessIds, String mapperBeanName, String tableName) {
if (permissionId == null) {
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
return false;
}
// 超级管理员直接放行
if (loginUser.getUser().getSuperAdmin() == 1) {
return true;
}
Object bean = SpringUtil.getBean(mapperBeanName);
if (bean instanceof ExtendMapper) {
Example userDataPermissionExample = new Example(UserDataPermission.class);
userDataPermissionExample.createCriteria().andEqualTo("status", 1).andEqualTo("tableName", tableName).andEqualTo("userId", loginUser.getUserId());
List<UserDataPermission> userDataPermissions = Optional.ofNullable(userDataPermissionMapper.selectByExample(userDataPermissionExample)).orElse(new ArrayList<>(16));
List<Integer> relatedIds = userDataPermissions.stream().map(UserDataPermission::getRelatedId).collect(Collectors.toList());
ExtendMapper mapper = (ExtendMapper) bean;
//查询本人的操作权限范围
Integer operationAuthority = getOperationAuthority(loginUser.getPermissionV2VoList(), permissionId);
List<Integer> operatorList = getOperatorList(businessIds, mapper);
switch (operationAuthority) {
case 1:
return CollectionUtils.isSubCollection(operatorList, Collections.singleton(loginUser.getUserId())) || CollectionUtils.isSubCollection(businessIds, relatedIds);
case 2:
// 获取部门的人员
Example example = new Example(UserV2.class);
example.createCriteria().andEqualTo("departmentId", loginUser.getUser().getDepartmentId());
List<Integer> userIds = Optional.of(userV2Mapper.selectByExample(example).stream().map(BaseEntity::getId).collect(Collectors.toList())).orElse(new ArrayList<>(16));
return CollectionUtils.isSubCollection(operatorList, userIds) || CollectionUtils.isSubCollection(businessIds, relatedIds);
case 3:
// 公司可见,直接返回true
return true;
default:
return false;
}
}
return false;
}
/** * 操作权限校验 * * @parampermission * @paramdataOwner * @return*/public boolean hasOperationAuthority(Integer permissionId, List<Integer> businessIds, String mapperBeanName) {
if (permissionId == null) {
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
return false;
}
// 超级管理员直接放行
if (loginUser.getUser().getSuperAdmin() == 1) {
return true;
}
Object bean = SpringUtil.getBean(mapperBeanName);
if (bean instanceof ExtendMapper) {
ExtendMapper mapper = (ExtendMapper) bean;
//查询本人的操作权限范围
Integer operationAuthority = getOperationAuthority(loginUser.getPermissionV2VoList(), permissionId);
List<Integer> operatorList = getOperatorList(businessIds, mapper);
switch (operationAuthority) {
case 1:
return CollectionUtils.isSubCollection(operatorList, Collections.singleton(loginUser.getUserId()));
case 2:
// 获取部门的人员
Example example = new Example(UserV2.class);
example.createCriteria().andEqualTo("departmentId", loginUser.getUser().getDepartmentId());
List<Integer> userIds = Optional.of(userV2Mapper.selectByExample(example).stream().map(BaseEntity::getId).collect(Collectors.toList())).orElse(new ArrayList<>(16));
return CollectionUtils.isSubCollection(operatorList, userIds);
case 3:
// 公司可见,直接返回true
return true;
default:
return false;
}
}
return false;
}
public List<Integer> getOperatorList(List<Integer> businessIds, ExtendMapper mapper) {
return businessIds.stream().map(businessId -> {
Object o = mapper.selectByPrimaryKey(businessId);
return (Integer) ReflectionUtil.getFieldValue(o, "operator");
}).distinct().collect(Collectors.toList());
}
public boolean dataAuthorityHandle() {
return false;
}
private Integer getOperationAuthority(List<PermissionV2Vo> permissionV2VoList, Integer permissionId) {
for (PermissionV2Vo permissionV2Vo : permissionV2VoList) {
if (permissionV2Vo.getId().equals(permissionId)) {
return permissionV2Vo.getOperationAuthority();
}
}
return 0;
}
}
需要控制的接口接入授权
java
/** * 修改账号信息 * * @paramuserV2 * @return*/
@PostMapping("modify")
@PreAuthorize("@auth.hasOperationAuthority(38,#userV2.id,'userV2Mapper')")
public BaseResponse modify(@RequestBody @Validated UserV2 userV2) {
userV2Service.modify(userV2);
return BaseResponse.successfulWithMessage("修改成功");
}