Spring security 介绍
省略,网上很多写的好的。
Spring Security 认证
代码使用的ruoyi的开源框架进行分析的,spring security配置可参考ruoyi-vue版的。
重点过滤器和类介绍
- 接口 UserDetailsService,需要实现里面的loadUserByUsername方法,用于通过用户名来查找数据库数据,后面认证管理器,会对其进行比对验证。
- 接口 UserDetails,需要实现一个该接口的一系列get方法,该类是接口UserDetailsService的返回值。
- 过滤器 UsernamePasswordAuthenticationFilter,继承了并实现了AbstractAuthenticationProcessingFilter的抽象方法attemptAuthentication,此方法作用:判断是否post提交,获取用户名密码,设置其他参数Details,交由认证管理器进行认证。该过滤器只有在SecurityConfig配置中配置了表单提交formLogin()才会进入。
- 认证者管理器 ProviderManager实现了认证管理器接口AuthenticationManager的authenticate方法,主要作用:找到对应的AuthenticationProvider,并调用该AuthenticationProvider的authenticate方法进行认证。
- 认证提供者 AbstractUserDetailsAuthenticationProvider,顾名思义,通过UserDetails来进行认证的,所以这个和UserDetailsService是有关系的。当ProviderManager找到此处理器后,会调用authenticate方法进行认证,该方法作用:通过数据库找到用户信息,通过配置的加密/解密器来校验密码。
- 认证提供者 DaoAuthenticationProvider实现了AbstractUserDetailsAuthenticationProvider的additionalAuthenticationChecks和retrieveUser方法,前者是用来处理校验密码的,后者是通过实现的UserDetailsService来找到用户信息的。在校验没有问题后,最后会调用createSuccessAuthentication方法,完成认证(中途如果没有抛出异常情况,就是认证成功)
认证流程
-
处理login接口:第一种在securityconfig中配置formlogin,表明表单提交,会经过UsernamePasswordAuthenticationFilter过滤器进行校验后,进入ProviderManager认证者管理器;第二种,没有配置formlogin,在控制层通过注入AuthenticationManager,使用手动认证,直接进入ProviderManager认证者管理器。
-
ProviderManager:进入后找到相应的提供者来进行认证。可自定义提供者Provider。
-
AbstractUserDetailsAuthenticationProvider:用于密码和身份认证。第一次使用缓存,如果认证失败,则重试一次,不使用缓存。
第一次
重试
-
DaoAuthenticationProvider:实现认证方法。成功后调用createSuccessAuthentication。
-
如果走的表单形式,调用了UsernamePasswordAuthenticationFilter,成功后会调用父类的AbstractAuthenticationProcessingFilter的successfulAuthentication方法,失败会调用unsuccessfulAuthentication方法。
后续访问使用jwt的token,重写继承spring的OncePerRequestFilter过滤器,从认证成功后存入redis的信息中获取到用户token,解析获取用户信息,封装成UsernamePasswordAuthenticationToken对象,提交给管理器,进行认证。每次访问都是要经过认证管理器进行认证的。
权限认证
ruoyi使用的一套很方便,自定义一个表达式,用于判断是否有权限访问。
package com.iwd.web.core.service;
import java.util.Set;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import com.iwd.common.core.domain.entity.SysRole;
import com.iwd.common.core.domain.model.LoginUser;
import com.iwd.common.utils.SecurityUtils;
import com.iwd.common.utils.StringUtils;
/**
* ruoyi首创 自定义权限实现,ss取自SpringSecurity首字母
*
* @author iwd
*/
@Service("ss")
public class PermissionService
{
/** 所有权限标识 */
private static final String ALL_PERMISSION = "*:*:*";
/** 管理员角色权限标识 */
private static final String SUPER_ADMIN = "admin";
private static final String ROLE_DELIMETER = ",";
private static final String PERMISSION_DELIMETER = ",";
/**
* 验证用户是否具备某权限
*
* @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;
}
return hasPermissions(loginUser.getPermissions(), permission);
}
/**
* 验证用户是否不具备某权限,与 hasPermi逻辑相反
*
* @param permission 权限字符串
* @return 用户是否不具备某权限
*/
public boolean lacksPermi(String permission)
{
return hasPermi(permission) != true;
}
/**
* 验证用户是否具有以下任意一个权限
*
* @param permissions 以 PERMISSION_NAMES_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;
}
Set<String> authorities = loginUser.getPermissions();
for (String permission : permissions.split(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 (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;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
{
return false;
}
for (String role : roles.split(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(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
}
}
在接口方法上,加入security的注解,只要注解中的方法返回true,则表明有权限,否则则没有权限。ruoyi也是根据表达式返回的值来实现的。