1. 登录用户细节配置
这是SysUser实体,继承BaseEntity类,该父类只是一个公共实体类的一些通用字段罢了,比如创建时间等。
与SpringSecurity无关。
1.1 SysUser实体
public class SysUser extends BaseEntity{//BaseEntity只有一些创建时间,更新时间的参数private static final long serialVersionUID = 1L;/** 用户ID */@Excel(name = "用户序号", cellType = Excel.ColumnType.NUMERIC, prompt = "用户编号")private Long userId;/** 部门ID */@Excel(name = "部门编号", type = Excel.Type.IMPORT)private Long deptId;/** 用户账号 */@Excel(name = "登录名称")private String userName;/** 用户昵称 */@Excel(name = "用户名称")private String nickName;/** 用户邮箱 */@Excel(name = "用户邮箱")private String email;/** 手机号码 */@Excel(name = "手机号码")private String phonenumber;/** 用户性别 */@Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")private String sex;/** 用户头像 */private String avatar;/** 密码 */private String password;/** 盐加密 */private String salt;/** 帐号状态(0正常 1停用) */@Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用")private String status;/** 删除标志(0代表存在 2代表删除) */private String delFlag;/** 最后登录IP */@Excel(name = "最后登录IP", type = Excel.Type.EXPORT)private String loginIp;/** 最后登录时间 */@Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Excel.Type.EXPORT)private Date loginDate;/** 部门对象 */@Excels({@Excel(name = "部门名称", targetAttr = "deptName", type = Excel.Type.EXPORT),@Excel(name = "部门负责人", targetAttr = "leader", type = Excel.Type.EXPORT)})private SysDept dept;/** 角色对象 */private List<SysRole> roles;/** 角色组 */private Long[] roleIds;/** 岗位组 */private Long[] postIds;/** 角色ID */private Long roleId;}
1.2 LoginUser
1.2.1 UserDetail接口
这是SpringSecurity的接口
public interface UserDetails extends Serializable {Collection<? extends GrantedAuthority> getAuthorities();//角色表,返回null,因为有其他更好的方式String getPassword();String getUsername();boolean isAccountNonExpired(); //账户是否过期?F可用boolean isAccountNonLocked();//是否解锁?T可用boolean isCredentialsNonExpired();//凭据是否过期 T可用boolean isEnabled();//账户是否可用 T可用}
1.2.2 LoginUser登录对象
- 提供核心用户信息,继承UserDetails接口
- 值得注意的是,这里的LoginUser中的SysUser字段中,还有一些权限相关的字段
- 与SpringSecurity相关。
/*** 登录用户身份权限** @author ruoyi*/@Datapublic class LoginUser implements UserDetails{private static final long serialVersionUID = 1L;/*** 用户ID*/private Long userId;/*** 部门ID*/private Long deptId;/*** 用户唯一标识*/private String token;/*** 登录时间*/private Long loginTime;/*** 过期时间*/private Long expireTime;/*** 登录IP地址*/private String ipaddr;/*** 登录地点*/private String loginLocation;/*** 浏览器类型*/private String browser;/*** 操作系统*/private String os;/*** 权限列表*/private Set<String> permissions;/*** 用户信息*/private SysUser user;public LoginUser(){}public LoginUser(SysUser user, Set<String> permissions){this.user = user;this.permissions = permissions;}public LoginUser(Long userId, Long deptId, SysUser user, Set<String> permissions){this.userId = userId;this.deptId = deptId;this.user = user;this.permissions = permissions;}@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;}}
1.3 UserDetailsService的业务层
UserDetail接口只是给我们声明自己的对象为UserDetail类型,但是SpringSecurity实际上是调用业务层。
1.3.1 UserDetailsService接口
因此我们需要实现这个业务层接口,只有一个方法。
简单来说,就是从数据取数据,封装成一个LoginUser给SpringSecurity验证
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
1.3.2 UserDetailsService实现类
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired
private ISysUserService userService;
@Autowired
private SysPermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
//从数据库获取User对象
SysUser user = userService.selectUserByUserName(username);
//其实如果LoginUser真不可用,SpringSecurity也会抛出异常。这里只是手动判断下,方便控制罢了
if (StringUtils.isNull(user))
{
log.info("登录用户:{} 不存在.", username);
throw new ServiceException("登录用户:" + username + " 不存在");
}
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
log.info("登录用户:{} 已被删除.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
}
else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
log.info("登录用户:{} 已被停用.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
}
return createLoginUser(user);
}
public UserDetails createLoginUser(SysUser user)
{
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}
}
1.3.3 SysPermissionService 用户权限处理
简言之,就是获取从数据库中获取该用户的权限信息罢了
@Component
public class SysPermissionService
{
@Autowired
private ISysRoleService roleService;
@Autowired
private 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
{
perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
}
return perms;
}
}
2.注解方式控制权限
2.1 注解形式
@PreAuthorize(“@ss.hasPermi(‘system:user:edit’)”)
PreAuthorize是SpringSecurity的注解,ss是业务层其中一个类的名字,hasPermi是ss类的方法
- 即调用ss.hasPermi,如果该方法返回true表示验证通过,否则不通过。
2.1.1 使用方法
在想要控制权限的地方加注解,在Controller中的一个方法加注解即可
@PreAuthorize("@ss.hasPermi('system:user:remove')")
@Log(title = "用户管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{userIds}")
public AjaxResult remove(@PathVariable Long[] userIds)
{
if (ArrayUtils.contains(userIds, getUserId()))
{
return error("当前用户不能删除");
}
return toAjax(userService.deleteUserByIds(userIds));
}
2.1.2 ss类
@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));
}
}
3.SpringSecurity配置类
因为AuthenticationManager注入到Spring容器了,我们可以手动获取这个对象,然后手动验证
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
/**
* 自定义用户认证逻辑
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 认证失败处理类
*/
@Autowired
private AuthenticationEntryPointImpl unauthorizedHandler;
/**
* 退出处理类
*/
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;
/**
* token认证过滤器
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
/**
* 跨域过滤器
*/
@Autowired
private CorsFilter corsFilter;
/**
* 解决 无法直接注入 AuthenticationManager
*
* @return
* @throws Exception
*/
@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", "/register", "/captchaImage").anonymous()
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/profile/**"
).permitAll()
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/druid/**").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
}
/**
* 强散列哈希加密实现
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder()
{
return new BCryptPasswordEncoder();
}
/**
* 身份认证接口
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}
使用 来验证账号与密码
// 用户验证
Authentication authentication = null;
try
{
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}catch (Exception e)
...比如账号密码不正确异常的信息
}
