Springboot Security框架快速集成(适用大部分业务项目)
liebian365 2024-11-22 17:16 3 浏览 0 评论
请关注我!分享文章皆源于项目实践。坚持原创,共同进步。
概述
一提到用户认证框架,很多人第一时间可能想到shiro,因为它小巧和易于理解。而用户认证的另一框架Spring security,却因为它的庞大与复杂,往往让初学者望而却步。但随着springboot单体应用的到来,突然间发现Spring security框架集成原来也可以很简单。本文向大家分享Spring Security在springboot项目中如何快速集成。希望对有这类应用场景需求的你有所帮助和价值。
用户认证框架,一般分为用户认证和用户授权两部分。本文将按这两个点来介绍。
用户认证
用户认证,就是用户的身份信息证明。认证过程即判定用户在系统中是否合法、有效或存在。
针对用户认证流程,一般系统的常用做法:
用户输入账号和密码,提交登录请求。后端接口验证密码合法性。若合法,生成登录token,并把token和用户信息(用户id、用户名等)作为key-value缓存到redis。为提高安全性,可以把token通过jwt加密后返回前端。前端拿到token加密串本地缓存,后续请求放到header中一起传给后端。后端通过检查token是否合法和超时,来决定是否需要用户重新登录认证。
了解了用户认证的流程。我们来看看springboot项目中如何集成。
maven依赖添加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
security bean config配置
/**
* spring security配置
*/
@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;
/**
* 解决 无法直接注入 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
// CRSF禁用,因为不使用session
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/captchaImage").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
/**
* 强散列哈希加密实现
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder()
{
return new BCryptPasswordEncoder();
}
/**
* 身份认证接口
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}
上述配置类继承spring WebSecurityConfigurerAdapter父类,并覆盖对应方法。
添加@EnableGlobalMethodSecurity注解,prePostEnabled=true属性开启用户权限注解(后续涉及)。
UserDetailsServiceImpl用户信息服务(需自定义实现,后续涉及),定义业务用户账号信息,一般通过db获取。
AuthenticationEntryPointImpl认证失败处理类,需继承框架AuthenticationEntryPoint接口,定义认证失败时,返回数据格式(HttpServletResponse根据业务自行定义返回错误提示,这里不再赘述)。
LogoutSuccessHandlerImpl登出处理类,需继承框架LogoutSuccessHandler接口,定义用户成功登出是的业务逻辑,比如登录成功redis缓存清除、登出日志记录等。这里不再赘述。
JwtAuthenticationTokenFilter认证token检查过滤器,需继承spring框架OncePerRequestFilter接口。后续涉及。
configure(HttpSecurity httpSecurity)方法中配置url拦截处理,细节参见代码注释。其中注意authenticationTokenFilter认证检查拦截器要添加到框架默认UsernamePasswordAuthenticationFilter.class拦截器前,只有这样才能生效。
BCryptPasswordEncoder bean定义用户账号密码加密方式,采用SHA-256 +随机盐+密钥对密码进行加密。
configure(AuthenticationManagerBuilder auth)方法中定义用户认证时,用户信息的获取来源以及密码加密方式。
UserDetailsServiceImpl定义
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
//业务service,从DB获取用户账号信息
@Autowired
private ISysUserService userService;
//业务service,获取用户对于菜单权限信息
@Autowired
private SysPermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
SysUser user = userService.selectUserByUserName(username);
if (StringUtils.isNull(user))
{
log.info("登录用户:{} 不存在.", username);
throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
}
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
log.info("登录用户:{} 已被删除.", username);
throw new BaseException("对不起,您的账号:" + username + " 已被删除");
}
else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
log.info("登录用户:{} 已被停用.", username);
throw new BaseException("对不起,您的账号:" + username + " 已停用");
}
return createLoginUser(user);
}
public UserDetails createLoginUser(SysUser user)
{
return new LoginUser(user, permissionService.getMenuPermission(user));
}
}
继承框架UserDetailsService接口,并实现loadUserByUsername(String username)方法,通过用户名获取用户账号身份信息。注意该方法返回为框架定义UserDetails对象。这里是LoginUser业务自定义实现。以下为参考实现,可根据业务自行定义相关属性并实现接口UserDetails方法。
/**
* 登录用户身份权限对象
*/
public class LoginUser implements UserDetails
{
/**
* 用户唯一标识
*/
private String token;
/**
* 登陆时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 权限列表
*/
private Set<String> permissions;
/**
* 用户信息
*/
private SysUser user;
public LoginUser(SysUser user, Set<String> permissions)
{
this.user = user;
this.permissions = permissions;
}
@JsonIgnore
@Override
public String getPassword()
{
return user.getPassword();
}
@Override
public String getUsername()
{
return user.getUserName();
}
/**
* 账户是否未过期,过期无法验证
*/
@JsonIgnore
@Override
public boolean isAccountNonExpired()
{
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
*
* @return
*/
@JsonIgnore
@Override
public boolean isAccountNonLocked()
{
return true;
}
/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
*
* @return
*/
@JsonIgnore
@Override
public boolean isCredentialsNonExpired()
{
return true;
}
/**
* 是否可用 ,禁用的用户不能身份验证
*
* @return
*/
@JsonIgnore
@Override
public boolean isEnabled()
{
return true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities()
{
return null;
}
// 省略各属性set/get方法...
}
JwtAuthenticationTokenFilter定义
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
//业务service,获取用户登录token信息。
@Autowired
private TokenService tokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException
{
//从http header获取token,通过redis key=token值得到用户登录缓存信息。
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser))
{
//校验token是否失效
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);
}
}
UsernamePasswordAuthenticationToken为框架定义认证类,通过构造参数把业务用户账号和授权信息建立关联。
最后设置通过框架工具类SecurityContextHolder.getContext().setAuthentication(authenticationToken);设置登录用户token上下文。该上下文若存在,表明用户认证状态有效,框架默认的认证检查filter会自动跳过。
最后定义用户登录接口认证判断。
/**
* 登录验证
*
* @param username 用户名
* @param password 密码
* @param code 验证码
* @return 结果
*/
public String login(String username, String password, String code, String uuid)
{
//省略基本参数校验逻辑...
// 用户验证
Authentication authentication = null;
try
{
//authenticationManager是框架AuthenticationManager类,在上面config配置类有定义。
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
catch (Exception e)
{
if (e instanceof BadCredentialsException)
{
//用户名/密码错误逻辑
throw new UserPasswordNotMatchException();
}
else
{
//失败异常逻辑
throw new CustomException(e.getMessage());
}
}
//获取认证成功用户信息
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
// 生成token、缓存loginUser到redis、jwt加密token
return tokenService.createToken(loginUser);
}
至此用户认证部分的主要逻辑定义完成。各业务逻辑请跟进具体项目场景自行定义。
用户授权
用户授权,即用户认证成功(身份合法)后,针对系统功能所拥有的权限。一般为系统菜单和页面按钮访问权限。后端系统层面,实际就是一个个不同的api接口访问权限。这里通过框架预置的注解@PreAuthorize来定义不同接口访问能力。
以获取菜单列表为例。
/**
* 获取菜单列表
*/
//接口权限注解定义
@PreAuthorize("@ss.hasPermi('system:menu:list')")
@GetMapping("/list")
public AjaxResult list(SysMenu menu)
{
LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
Long userId = loginUser.getUser().getUserId();
List<SysMenu> menus = menuService.selectMenuList(menu, userId);
return AjaxResult.success(menus);
}
接口方法添加注解,设置相关权限定义:@PreAuthorize("@ss.hasPermi('system:menu:list')")
注解值为一个公共权限处理方法类,该方法接收一个权限字符串,只有拥有该字符串的用户才能访问该接口。
公共权限处理方法定义
@Service("ss")
public class PermissionService
{
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission)
{
if (StringUtils.isEmpty(permission))
{
return false;
}
//http header获取token,并从redis中拿到用户登录缓存信息
LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
{
return false;
}
Set<String> permissions = loginUser.getPermissions();
//用户权限判断
return permissions.contains(StringUtils.trim(permission));
}
}
总结
本文介绍了springboot security快速集成的方法,分为用户认证和用户授权两部分。集合框架接口定义业务数据来源和规则处理完成项目集成。
整个实现逻辑参考了开源项目RuoYi-Vue的代码实现。特别感谢项目作者!让我有机会从项目中了解spring security的集成。
相关推荐
- 快递查询教程,批量查询物流,一键管理快递
-
作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...
- 一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递
-
对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...
- 快递查询单号查询,怎么查物流到哪了
-
输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...
- 3分钟查询物流,教你一键批量查询全部物流信息
-
很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...
- 快递单号查询,一次性查询全部物流信息
-
现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...
- 快递查询工具,批量查询多个快递快递单号的物流状态、签收时间
-
最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...
- 快递查询软件,自动识别查询快递单号查询方法
-
当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...
- 教你怎样查询快递查询单号并保存物流信息
-
商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...
- 简单几步骤查询所有快递物流信息
-
在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...
- 物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号
-
最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...
- 连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息
-
快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...
- 快递查询教程,快递单号查询,筛选更新量为1的单号
-
最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...
- 掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析
-
在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...
- 从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息
-
在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...
- 物流单号查询,在哪里查询快递
-
如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)