Springboot Security框架快速集成(适用大部分业务项目)
liebian365 2024-11-22 17:16 19 浏览 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的集成。
相关推荐
- “版本末期”了?下周平衡补丁!国服最强5套牌!上分首选
-
明天,酒馆战棋就将迎来大更新,也聊了很多天战棋相关的内容了,趁此机会,给兄弟们穿插一篇构筑模式的卡组推荐!老规矩,我们先来看10职业胜率。目前10职业胜率排名与一周前基本类似,没有太多的变化。平衡补丁...
- VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符"
-
首先,程序中头文件的选择,要选择头文件,在文件中是没有对M_PI的定义的。选择:项目——>”XXX属性"——>配置属性——>C/C++——>预处理器——>预处理器定义,...
- 东营交警实名曝光一批酒驾人员名单 88人受处罚
-
齐鲁网·闪电新闻5月24日讯酒后驾驶是对自己和他人生命安全极不负责的行为,为守护大家的平安出行路,东营交警一直将酒驾作为重点打击对象。5月23日,东营交警公布最新一批饮酒、醉酒名单。对以下驾驶人醉酒...
- Qt界面——搭配QCustomPlot(qt platform)
-
这是我第一个使用QCustomPlot控件的上位机,通过串口精确的5ms发送一次数据,再将读取的数据绘制到图表中。界面方面,尝试卡片式设计,外加QSS简单的配了个色。QCustomPlot官网:Qt...
- 大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写
-
老友相聚,仗剑江湖!《大话西游2》2021全民PK季4月激燃打响,各PK玩法鏖战齐开,零门槛参与热情高涨。PK季期间,不仅各种玩法奖励丰厚,参与PK趣闻录活动,投稿自己在PK季遇到的趣事,还有机会带走...
- 测试谷歌VS Code AI 编程插件 Gemini Code Assist
-
用ClaudeSonnet3.7的天气测试编码,让谷歌VSCodeAI编程插件GeminiCodeAssist自动编程。生成的文件在浏览器中的效果如下:(附源代码)VSCode...
- 顾爷想知道第4.5期 国服便利性到底需优化啥?
-
前段时间DNF国服推出了名为“阿拉德B计划”的系列改版计划,截至目前我们已经看到了两项实装。不过关于便利性上,国服似乎还有很多路要走。自从顾爷回归DNF以来,几乎每天都在跟我抱怨关于DNF里面各种各样...
- 掌握Visual Studio项目配置【基础篇】
-
1.前言VisualStudio是Windows上最常用的C++集成开发环境之一,简称VS。VS功能十分强大,对应的,其配置系统较为复杂。不管是对于初学者还是有一定开发经验的开发者来说,捋清楚VS...
- 还嫌LED驱动设计套路深?那就来看看这篇文章吧
-
随着LED在各个领域的不同应用需求,LED驱动电路也在不断进步和发展。本文从LED的特性入手,推导出适合LED的电源驱动类型,再进一步介绍各类LED驱动设计。设计必读:LED四个关键特性特性一:非线...
- Visual Studio Community 2022(VS2022)安装图文方法
-
直接上步骤:1,首先可以下载安装一个VisualStudio安装器,叫做VisualStudioinstaller。这个安装文件很小,很快就安装完成了。2,打开VisualStudioins...
- Qt添加MSVC构建套件的方法(qt添加c++11)
-
前言有些时候,在Windows下因为某些需求需要使用MSVC编译器对程序进行编译,假设我们安装Qt的时候又只是安装了MingW构建套件,那么此时我们该如何给现有的Qt添加一个MSVC构建套件呢?本文以...
- Qt为什么站稳c++GUI的top1(qt c)
-
为什么现在QT越来越成为c++界面编程的第一选择,从事QT编程多年,在这之前做C++界面都是基于MFC。当时为什么会从MFC转到QT?主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MF...
- qt开发IDE应该选择VS还是qt creator
-
如果一个公司选择了qt来开发自己的产品,在面临IDE的选择时会出现vs或者qtcreator,选择qt的IDE需要结合产品需求、部署平台、项目定位、程序猿本身和公司战略,因为大的软件产品需要明确IDE...
- Qt 5.14.2超详细安装教程,不会来打我
-
Qt简介Qt(官方发音[kju:t],音同cute)是一个跨平台的C++开库,主要用来开发图形用户界面(GraphicalUserInterface,GUI)程序。Qt是纯C++开...
- Cygwin配置与使用(四)——VI字体和颜色的配置
-
简介:VI的操作模式,基本上VI可以分为三种状态,分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:1)...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- “版本末期”了?下周平衡补丁!国服最强5套牌!上分首选
- VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符"
- 东营交警实名曝光一批酒驾人员名单 88人受处罚
- Qt界面——搭配QCustomPlot(qt platform)
- 大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写
- 测试谷歌VS Code AI 编程插件 Gemini Code Assist
- 顾爷想知道第4.5期 国服便利性到底需优化啥?
- 掌握Visual Studio项目配置【基础篇】
- 还嫌LED驱动设计套路深?那就来看看这篇文章吧
- Visual Studio Community 2022(VS2022)安装图文方法
- 标签列表
-
- 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)