Spring Security单项目权限设计过程解析

为什么选择SpringSecurity?

现如今,在JavaWeb的世界里Spring可以说是一统江湖,随着微服务的到来,SpringCloud可以说是Java程序员必须熟悉的框架,就连阿里都为SpringCloud写开源呢。(比如大名鼎鼎的Nacos)作为Spring的亲儿子,SpringSecurity很好的适应了了微服务的生态。你可以非常简便的结合Oauth做认证中心服务。本文先从最简单的单体项目开始,逐步掌握Security。更多可达官方文档

准备

我准备了一个简单的demo,具体代码会放到文末。提前声明,本demo没有用JWT,因为我想把token的维护放到服务端,更好的维护过期时间。(当然,如果将来微服务认证中心的形式,JWT也可以做到方便的维护过期时间,不做过多讨论)如果想了解Security+JWT简易入门,请戳

本项目结构如下

另外,本demo使用了MybatisPlus、lombok。

核心代码

首先需要实现两个类,一个是UserDetails的实现类SecurityUser,一个是UserDetailsService的实现类SecurityUserService。

**
 * Security 要求需要实现的User类
 * */
@Data
public class SecurityUser implements UserDetails {
 @Autowired
 private SysRoleService sysRoleService;
 //用户登录名(注意此处的username和SysUser的loginName是一个值)
 private String username;
 //登录密码
 private String password;
 //用户id
 private SysUser sysUser;
 //该用户的所有权限
 private List<SysMenu> sysMenuList;
 /**构造函数*/
 public SecurityUser(SysUser sysUser){
  this.username = sysUser.getLoginName();
  this.password = sysUser.getPassword();
  this.sysUser = sysUser;
 }
 public SecurityUser(SysUser sysUser,List<SysMenu> sysMenuList){
  this.username = sysUser.getLoginName();
  this.password = sysUser.getPassword();
  this.sysMenuList = sysMenuList;
  this.sysUser = sysUser;
 }
 /**需要实现的方法*/
 @Override
 public Collection<? extends GrantedAuthority> getAuthorities() {
  List<GrantedAuthority> authorities = new ArrayList<>();
  for(SysMenu menu : sysMenuList) {
   authorities.add(new SimpleGrantedAuthority(menu.getPerms()));
  }
  return authorities;
 }
 @Override
 public String getPassword() {
  return this.password;
 }
 @Override
 public String getUsername() {
  return this.username;
 }
 //默认账户未过期
 @Override
 public boolean isAccountNonExpired() {
  return true;
 }
 //默认账户没有带锁
 @Override
 public boolean isAccountNonLocked() {
  return true;
 }
 //默认凭证没有过期
 @Override
 public boolean isCredentialsNonExpired() {
  return true;
 }
 //默认账户可用
 @Override
 public boolean isEnabled() {
  return true;
 }
}

这个类包含着某个请求者的信息,在Security中叫做主体。其中这个方法是必须实现的,可以获取用户的具体权限。我们这边权限的颗粒度达到了菜单级别,而不是很多开源项目中角色那级别,我觉得颗粒度越细越方便(个人觉得...)

/**
 * Security 要求需要实现的UserService类
 * */
@Service
public class SecurityUserService implements UserDetailsService{

 @Autowired
 private SysUserService sysUserService;
 @Autowired
 private SysMenuService sysMenuService;
 @Autowired
 private HttpServletRequest httpServletRequest;

 @Override
 public SecurityUser loadUserByUsername(String loginName) throws UsernameNotFoundException {
  LambdaQueryWrapper<SysUser> condition = Wrappers.<SysUser>lambdaQuery().eq(SysUser::getLoginName, loginName);
  SysUser sysUser = sysUserService.getOne(condition);
  if (Objects.isNull(sysUser)){
   throw new UsernameNotFoundException("未找到该用户!");
  }
  Long projectId = null;
  try{
   projectId = Long.parseLong(httpServletRequest.getHeader("projectId"));
  }catch (Exception e){

  }
  SysMenuModel sysMenuModel;
  if (sysUser.getUserType()){
   sysMenuModel = new SysMenuModel();
  }else {
   sysMenuModel = new SysMenuModel().setUserId(sysUser.getId());
  }
  sysMenuModel.setProjectId(projectId);
  List<SysMenu> menuList = sysMenuService.getList(sysMenuModel);
  return new SecurityUser(sysUser,menuList);
 }
}

显而易见,这个类实现了唯一的方法loadUserByUsername,从而可以拿到某用户的所有权限,并生成主体,在后面的filter中就可以见到他的作用了。

在看配置和filter之前,还有一个类需要说明一下,此类提供方法,可以让用户未登录、或者token失效的情况下进行统一返回。

@Component
public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

 private static final long serialVersionUID = 1L;

 @Override
 public void commence(HttpServletRequest request, HttpServletResponse response,
       AuthenticationException authException) throws IOException, ServletException {
  response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"token失效,请登陆后重试");
 }
}

ok,接下来看配置,实现了WebSecurityConfigurerAdapter的SecurityConfig类,特别说明,本demo算是前后端分离的前提下写的,所以实现过多的方法,其实这个类可以实现三个方法。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{

 @Autowired
 SecurityAuthenticationEntryPoint securityAuthenticationEntryPoint;
 @Autowired
 SecurityFilter securityFilter;

 @Override
 protected void configure(HttpSecurity http) throws Exception {
  http
    //禁止csrf
    .csrf().disable()
    //异常处理
    .exceptionHandling().authenticationEntryPoint(securityAuthenticationEntryPoint).and()
    //Session管理方式
    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
    //开启认证
    .authorizeRequests()
    .antMatchers("/login/login").permitAll()
    .antMatchers("/login/register").permitAll()
    .antMatchers("/login/logout").permitAll()
    .anyRequest().authenticated();
  http
    .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class);
 }
}

异常处理就是上面那个类,Session那几种管理方式我在那篇Security+JWT的文章中也有所讲解,比较简单,然后是几个不用验证的登录路径,剩下的都需要经过我们下面这个filter。

@Slf4j
@Component
public class SecurityFilter extends OncePerRequestFilter {

 @Autowired
 SecurityUserService securityUserService;
 @Autowired
 SysUserService sysUserService;
 @Autowired
 SysUserTokenService sysUserTokenService;

 /**
  * 认证授权
  * */
 @Override
 protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
         FilterChain filterChain) throws ServletException, IOException {
  log.info("访问的链接是:{}",httpServletRequest.getRequestURL());
  try {
   final String token = httpServletRequest.getHeader("token");
   LambdaQueryWrapper<SysUserToken> condition = Wrappers.<SysUserToken>lambdaQuery().eq(SysUserToken::getToken, token);
   SysUserToken sysUserToken = sysUserTokenService.getOne(condition);
   if (Objects.nonNull(sysUserToken)){
    SysUser sysUser = sysUserService.getById(sysUserToken.getUserId());
    if (Objects.nonNull(sysUser)){
     SecurityUser securityUser = securityUserService.loadUserByUsername(sysUser.getLoginName());
     //将主体放入内存
     UsernamePasswordAuthenticationToken authentication =
       new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
     authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
     //放入内存中去
     SecurityContextHolder.getContext().setAuthentication(authentication);
    }
   }
  }catch (Exception e){
   log.error("认证授权时出错:{}", Arrays.toString(e.getStackTrace()));
  }
  filterChain.doFilter(httpServletRequest, httpServletResponse);
 }
}

判断用户是否登录,就是从数据库中查看是否有未过期的token,如果存在,就把主体信息放进到项目的内存中去,特别说明的是,每个请求链结束,SecurityContextHolder.getContext()的数据都会被clear的,所以,每次请求的时候都需要set。

以上就完成了Security核心的创建,为了业务代码方便获取内存中的主体信息,我特意加了一个获取用户信息的方法

/**
 * 获取Security主体工具类
 * @author pjjlt
 * */
public class SecurityUserUtil {
 public static SysUser getCurrentUser(){
  SecurityUser securityUser = (SecurityUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  if (Objects.nonNull(securityUser) && Objects.nonNull(securityUser.getSysUser())){
   return securityUser.getSysUser();
  }
  return null;
 }
}

业务代码

以上是Security核心代码,下面简单加两个业务代码,比如登录和某个接口的权限访问测试。

万物之源登录登出

首先,不被filter拦截的那三个方法注册、登录、登出,我都写在了moudle.controller.LoginController这个路径下,注册就不用说了,就是一个insertUser的方法,做好判断就好,密码通过AES加个密。

下面看下登录代码,controller层就不说了,反正就是个验参。

 /**
  * 登录,返回登录信息,前端需要缓存
  * */
 @Override
 @Transactional(rollbackFor = Exception.class)
 public JSONObject login(SysUserModel sysUserModel) throws Exception{
  JSONObject result = new JSONObject();
  //1. 验证账号是否存在、密码是否正确、账号是否停用
  Wrapper<SysUser> sysUserWrapper = Wrappers.<SysUser>lambdaQuery()
    .eq(SysUser::getLoginName,sysUserModel.getLoginName()).or()
    .eq(SysUser::getEmail,sysUserModel.getEmail());
  SysUser sysUser = baseMapper.selectOne(sysUserWrapper);
  if (Objects.isNull(sysUser)){
   throw new Exception("用户不存在!");
  }
  String password = CipherUtil.encryptByAES(sysUserModel.getPassword());
  if (!password.equals(sysUser.getPassword())){
   throw new Exception("密码不正确!");
  }
  if (sysUser.getStatus()){
   throw new Exception("账号已删除或已停用!");
  }
  // 2.更新最后登录时间
  sysUser.setLoginIp(ServletUtil.getClientIP(request));
  sysUser.setLoginDate(LocalDateTime.now());
  baseMapper.updateById(sysUser);
  // 3.封装token,返回信息
  String token = UUID.fastUUID().toString().replace("-","");
  LocalDateTime expireTime = LocalDateTime.now().plusSeconds(expireTimeSeconds);
  SysUserToken sysUserToken = new SysUserToken()
    .setToken(token).setUserId(sysUser.getId()).setExpireTime(expireTime);
  sysUserTokenService.save(sysUserToken);
  result.putOpt("token",token);
  result.putOpt("expireTime",expireTime);
  return result;
 }

首先验证下用户是否存在,登录密码是否正确,然后封装token,值得一提的是,我并没有从数据库(sysUserToken)中获取用户已经登录的token,然后更新过期时间的形式做登录,而是每次登录都获取新token,这样就可以做到多端登录了,后期还可以做账号登录数量的控制。

然后就是登出,删除库中存在的token

 /**
  * 登出,删除token
  * */
 @Override
 public void logout() throws Exception{
  String token = httpServletRequest.getHeader("token");
  if (Objects.isNull(token)){
   throw new LoginException("token不存在",ResultEnum.LOGOUT_ERROR);
  }
  LambdaQueryWrapper<SysUserToken> sysUserWrapper = Wrappers.<SysUserToken>lambdaQuery()
    .eq(SysUserToken::getToken,token);
  baseMapper.delete(sysUserWrapper);
 }

权限验证

这边我维护了两个账号,一个是超级管理员majian,拥有所有权限。一个是普通人员_pjjlt,只有一些权限,我们看一下访问接口的效果。

我们访问的接口是moudle.controller.LoginController路径下的

@PreAuthorize("hasAnyAuthority('test')")
@GetMapping("test")
public String test(){
 return "test";
}

其中hasAnyAuthority('test')就是权限码

我们模拟用不同账号访问,就是改变请求header中的token值,就是登录阶段返回给前端的token。

首先是超级管理员验证

然后是普通管理员访问

接着没有登录(token不存在或者已过期)访问

demo地址

https://github.com/majian1994/easy-file-back

结束语

本文简单讲解了,主要是将Security相关的东西,具体实现角色的三要素,用户、角色、权限(菜单)可以看我的代码,都写完测完了,本来想写个文档管理系统,帮助我司更好的管理接口文档,but有位小伙伴找了一个不错的开源的了,所以这代码就成了我的一个小demo。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 基于Spring Security的Oauth2授权实现方法

    前言 经过一段时间的学习Oauth2,在网上也借鉴学习了一些大牛的经验,推荐在学习的过程中多看几遍阮一峰的<理解OAuth 2.0>,经过对Oauth2的多种方式的实现,个人推荐Spring Security和Oauth2的实现是相对优雅的,理由如下: 1.相对于直接实现Oauth2,减少了很多代码量,也就减少的查找问题的成本. 2.通过调整配置文件,灵活配置Oauth相关配置. 3.通过结合路由组件(如zuul),更好的实现微服务权限控制扩展. Oauth2概述 oauth2根据使用场景不同

  • Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制

    思路 : 动态路由实现:在导航守卫中判断用户是否有用户信息, 通过调用接口,拿到后台根据用户角色生成的菜单树, 格式化菜单树结构信息并递归生成层级路由表并 使用Vuex保存,通过  router.addRoutes  动态挂载到  router  上,按钮级别的权限控制,则需使用自定义指令去实现. 实现: 导航守卫代码: router.beforeEach((to, from, next) => { NProgress.start() // start progress bar to.meta

  • 使用Spring Security OAuth2实现单点登录

    1.概述 在本教程中,我们将讨论如何使用Spring Security OAuth和Spring Boot实现SSO - 单点登录. 我们将使用三个单独的应用程序: •授权服务器 - 这是中央身份验证机制 •两个客户端应用程序:使用SSO的应用程序 非常简单地说,当用户试图访问客户端应用程序中的安全页面时,他们将被重定向到首先通过身份验证服务器进行身份验证. 我们将使用OAuth2中的授权代码授权类型来驱动身份验证委派. 2.客户端应用程序 让我们从客户端应用程序开始;当然,我们将使用Sprin

  • SpringBoot+Spring Security+JWT实现RESTful Api权限控制的方法

    摘要:用spring-boot开发RESTful API非常的方便,在生产环境中,对发布的API增加授权保护是非常必要的.现在我们来看如何利用JWT技术为API增加授权保护,保证只有获得授权的用户才能够访问API. 一:开发一个简单的API 在IDEA开发工具中新建一个maven工程,添加对应的依赖如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-b

  • Spring security实现登陆和权限角色控制

     随笔简介 1.spring版本:4.3.2.RELEASE+spring security 版本:4.1.2.RELEASE(其它不做说明) 2.所展示内容全部用注解配置 3.springmvc已经配置好,不作说明 4.会涉及到springmvc,spel,el的东西,不熟悉的同学可以先去看一下这方面内容,特别是springmvc 首先想一下,登陆需要什么,最简单的情况下,用户名,密码,然后比对数据库,如果吻合就跳转到个人页面,否则回到登陆页面,并且提示用户名密码错误.这个过程中应该还带有权限

  • spring Security的自定义用户认证过程详解

    首先我需要在xml文件中声明.我要进行自定义用户的认证类,也就是我要自己从数据库中进行查询 <http pattern="/*.html" security="none"/> <http pattern="/css/**" security="none"/> <http pattern="/img/**" security="none"/> <h

  • 话说Spring Security权限管理(源码详解)

    最近项目需要用到Spring Security的权限控制,故花了点时间简单的去看了一下其权限控制相关的源码(版本为4.2). AccessDecisionManager spring security是通过AccessDecisionManager进行授权管理的,先来张官方图镇楼. AccessDecisionManager AccessDecisionManager 接口定义了如下方法: //调用AccessDecisionVoter进行投票(关键方法) void decide(Authent

  • java中自定义Spring Security权限控制管理示例(实战篇)

    背景描述 项目中需要做细粒的权限控制,细微至url + httpmethod (满足restful,例如: https://.../xxx/users/1, 某些角色只能查看(HTTP GET), 而无权进行增改删(POST, PUT, DELETE)). 表设计 为避嫌,只列出要用到的关键字段,其余敬请自行脑补. 1.admin_user 管理员用户表, 关键字段( id, role_id ). 2.t_role 角色表, 关键字段( id, privilege_id ). 3.t_privi

  • Spring security实现权限管理示例

    Spring security实现权限管理示例,具体如下: 1.配置文件 1.POM.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.o

  • Spring Security单项目权限设计过程解析

    为什么选择SpringSecurity? 现如今,在JavaWeb的世界里Spring可以说是一统江湖,随着微服务的到来,SpringCloud可以说是Java程序员必须熟悉的框架,就连阿里都为SpringCloud写开源呢.(比如大名鼎鼎的Nacos)作为Spring的亲儿子,SpringSecurity很好的适应了了微服务的生态.你可以非常简便的结合Oauth做认证中心服务.本文先从最简单的单体项目开始,逐步掌握Security.更多可达官方文档 准备 我准备了一个简单的demo,具体代码会

  • 基于spring security实现登录注销功能过程解析

    这篇文章主要介绍了基于spring security实现登录注销功能过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.引入maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependenc

  • Spring Security OAuth2 token权限隔离实例解析

    这篇文章主要介绍了Spring Security OAuth2 token权限隔离实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 由于项目OAuth2采用了多种模式,授权码模式为第三方系统接入,密码模式用于用户登录,Client模式用于服务间调用, 所有不同的模式下的token需要用 @PreAuthorize("hasAuthority('client')") 进行隔离,遇到问题一直验证不通过. 通过调试发现资源服务从授权服

  • Spring security用户URL权限FilterSecurityInterceptor使用解析

    这篇文章主要介绍了Spring security用户URL权限FilterSecurityInterceptor使用解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 用户通过浏览器发送URL地址,由FilterSecurityInterceptor判断是否具有相应的访问权限. 对于用户请求的方法权限,例如注解@PreAuthorize("hasRole('ADMIN')"),由MethodSecurityInterceptor判断

  • Spring Security基于数据库实现认证过程解析

    创建数据库 SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for role -- ---------------------------- DROP TABLE IF EXISTS `role`; CREATE TABLE `role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, `nam

  • Spring Security 中细化权限粒度的方法

    有小伙伴表示微人事(https://github.com/lenve/vhr)的权限粒度不够细.不过松哥想说的是,技术都是相通的,明白了 vhr 中权限管理的原理,在此基础上就可以去细化权限管理粒度,细化过程和还是用的 vhr 中用的技术,只不过设计层面重新规划而已. 当然今天我想说的并不是这个话题,主要是想和大家聊一聊 Spring Security 中权限管理粒度细化的问题.因为这个问题会涉及到不同的权限管理模型,今天和小伙伴们聊一聊- 1.权限管理模型 要想将细化权限粒度,我们不可避免会涉

  • 详解Spring Security如何在权限中使用通配符

    目录 前言 1. SpEL 2. 自定义权限该如何写 3. 权限通配符 4. TienChin 项目怎么做的 前言 小伙伴们知道,在 Shiro 中,默认是支持权限通配符的,例如系统用户有如下一些权限: system:user:add system:user:delete system:user:select system:user:update … 现在给用户授权的时候,我们可以像上面这样,一个权限一个权限的配置,也可以直接用通配符: system:user:* 这个通配符就表示拥有针对用户的

  • Spring AOP AspectJ使用及配置过程解析

    这篇文章主要介绍了Spring AOP AspectJ使用及配置过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 AspectJ是一个基于Java语言的AOP框架,Spring2.0以后新增了对AspectJ切点表达式支持.因为Spring1.0的时候Aspectj还未出现; AspectJ1.5中新增了对注解的支持,允许直接在Bean类中定义切面.新版本的Spring框架建 议我们都使用AspectJ方式来开发AOP,并提供了非常灵活且

  • Spring Security自定义登录页面认证过程常用配置

    目录 一.自定义登录页面 1.编写登录页面 2.修改配置类 3.编写控制器 二. 认证过程其他常用配置 1.失败跳转 1.1编写页面 1.2修改表单配置 1.3添加控制器方法 1.4设置fail.html不需要认证 2.设置请求账户和密码的参数名 2.1源码简介 2.2修改配置 2.3修改页面 3.自定义登录成功处理器 3.1源码分析 3.2代码实现 4.自定义登录失败处理器 4.1源码分析 4.2代码实现 一.自定义登录页面 虽然Spring Security给我们提供了登录页面,但是对于实际

  • Spring Boot2配置服务器访问日志过程解析

    这篇文章主要介绍了Spring Boot2配置服务器访问日志过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Tomcat控制台中看到的日志是服务器的日志,而服务器访问日志则是记录服务处理的请求信息. 开发环境:IntelliJ IDEA 2019.2.2 Spring Boot版本:2.1.8 1.新建一个名称为demo的Spring Boot项目. 2.application.yml 添加配置 server: tomcat: base

随机推荐