spring-security-结合JWT的简单demo
spring-security-demo
前言:本来是想尽量简单简单点的写一个demo的,但是spring-security实在是内容有点多,写着写着看起来就没那么简单了,想入门spring-security的话还是需要下些功夫的,这远没有Mybatis、JPA之类的容易入门
一个spring-security采用jwt认证机制的demo。
以下代码仅为说明代码作用,有的并不完整,如若要参考请git clone整个项目代码查看
参考:
spring security学习(SpringBoot2.1.5版本)
SpringBootSecurity学习(13)前后端分离版之JWT
重拾后端之Spring Boot(四):使用JWT和Spring Security保护REST API
spring-security
config.securityConfig是springSecurity的安全配置类,在这个类中配置需要验证的接口、需要放行的接口,配置登录成功失败的处理器
1.最简单的用户角色权限控制demo
最简单是demo是直接在securityConfig中配置存在内存中的用户对象,可以采用一下代码配置用户角色:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("user").password({noop}123).roles("USER")
.and()
.withUser("admin").password({noop}123).roles("ADMIN")
.and()
.withUser("one").password({noop}123).roles("ONE")
.and()
.withUser("two").password({noop}123).roles("TWO");
}
然后在securityConfig加注解开启接口的preAuth注解支持
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true,jsr250Enabled = true)
public class securityConfig extends WebSecurityConfigurerAdapter {
然后可以直接在Controller的接口上加注解
/* 只有角色ONE才能访问 */
@PreAuthorize("hasRole('ONE')")
@GetMapping("/hello")
public String hello(){
return "hello Spring Security";
}
然后访问localhost:8080/two,发现会跳转到login登录页面,此时以one登录进去可以正常访问,但是以其它角色访问均会出错。至此,最简单的demo已完成。
2.修改用户为数据库用户
上面的用户是存在内存中的,接下来需要将其改为从数据库中获取用户信息并验证。
首先需要在securityConfig中配置spring-security加载用户时使用的类,spring-security通过我们提供的这个类得到一个用户信息,该用户信息中一般包含用户名、密码、角色,spring-security得到这些信息后完成后续操作。
提供该类给securityConfig
@Qualifier("userDetailServiceImpl")
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService) // 提供给spring-security的类
.passwordEncoder( new BCryptPasswordEncoder() ); // 这是密码加密的类,可以理解为将明文密码加密成hash值,可以先忽略照写
}
然后需要实现这个类
@Component
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserPasswordRepository userPasswordRepository;
@Autowired
private UserRoleRepository userRoleRepository;
/**
* 我的数据库表分为User表、UserInfo用户详细信息表、UserPassword密码表、UserRole用户角色表
* spring-security会给这个方法提供一个用户名,然后我们实现根据用户名得到这个用户的UserDetail信息(类似于包含用户名、密码、角色的实体类,下一步重写它)
* 然后返回的就是这个UserDetail,spring-security可以使用该类完成其它的操作
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findFirstByUsername(username);
Integer id = user.getId();
if (Objects.nonNull(user) && username.trim().length() <= 0) {
throw new UsernameNotFoundException("用户名错误");
}
// 填充所有角色信息
List<GrantedAuthorityImpl> grantedAuthorities = new ArrayList<>();
List<UserRole> roles = userRoleRepository.findByCreator_Id(id);
for (UserRole role : roles) {
grantedAuthorities.add(new GrantedAuthorityImpl("ROLE_" + role.getRole()));
}
return new UserDetailImpl(
username,
userPasswordRepository.findByCreator_Id(id).getPassword(),
grantedAuthorities
);
}
}
实现UserDetail,这个类就像是一个实体类,但是实现了UserDetails接口,遵循spring-security的规范以让spring-security能使用它
@NoArgsConstructor
@ToString
public class UserDetailImpl implements UserDetails {
private String username;
@JsonIgnore
private String password;
private List<GrantedAuthorityImpl> authorities;
@JsonIgnore
private boolean accountNonExpired;
@JsonIgnore
private boolean accountNonLocked;
@JsonIgnore
private boolean credentialsNonExpired;
@JsonIgnore
private boolean enabled;
public UserDetailImpl(String username, String password, List<GrantedAuthorityImpl> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
this.accountNonExpired = true;
this.accountNonLocked = true;
this.credentialsNonExpired = true;
this.enabled = true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
最好再实现一下GrantedAuthority,这个是角色信息的规范接口
@NoArgsConstructor
public class GrantedAuthorityImpl implements GrantedAuthority {
private String authority;
public GrantedAuthorityImpl(String authority) {
this.authority = authority;
}
@Override
public String getAuthority() {
return authority;
}
}
上述这些就完成了spring-security用户表转移到数据库的操作了。
3.引入jwt
上述过程中,spring-security默认使用session-cookie的方法保存一个连接中的用户信息,然后拿这些用户信息到数据库查询。接下来可以改造成为jwt保存用户信息,jwt其实就是平时经常看到的token保存用户信息,其机制是直接将用户信息写在token中,然后就这个token进行签名后颁发给用户,用户发起请求时可以携带token,服务器就可以直接给用户认证信息了。
首先我们先来构造jwt token。
首先是jwt的工具类,该类提供信息HMACSHA256加密、信息签名、测试token是否合法
public class JWTUtils {
public static final String DEFAULT_HEADER = "\"alg\":\"HS256\",\"typ\":\"JWT\"";
public static final String SECRET = "woshizengchunmiao";
public static final long EXPIRE_TIME = 1000 * 60 * 60 * 24;
public static final String HEADER_TOKEN_NAME = "Authrization";
public static String encode(String input) {
return Base64.getEncoder().encodeToString(input.getBytes());
}
public static String decode(String input) {
return new String(Base64.getDecoder().decode(input));
}
public static String HMACSHA256(String data, String secret) throws Exception {
Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
hmacSHA256.init(secretKeySpec);
byte[] bytes = hmacSHA256.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte aByte : bytes) {
sb.append(Integer.toHexString((aByte & 0xFF) | 0x100), 1, 3);
}
return sb.toString().toUpperCase();
}
public static String getSignature(String payload) throws Exception {
return HMACSHA256(encode(DEFAULT_HEADER) + encode(payload), SECRET);
}
public static String testJwt(String jwt) {
String[] split = jwt.split("\\.");
try {
if (!(HMACSHA256(split[0] + split[1], SECRET).equals(split[2]))) {
return null;
}
if (!decode(split[0]).equals(DEFAULT_HEADER)) {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return decode(split[1]);
}
}
然后提供一个JWT类,构造该类时,只需要将想放在token上的信息传入构造函数,即可得到一个想要的JWT,调用toString方法就得到了token
public class JWT {
private String header;
private String payload;
private String signature;
public JWT(String payload) throws Exception {
this.payload = JWTUtils.encode(payload);
this.header = JWTUtils.encode(JWTUtils.DEFAULT_HEADER);
this.signature = JWTUtils.getSignature(payload);
}
@Override
public String toString() {
return header + "." + payload + "." + signature;
}
}
4.jwt设置到spring-security
以上两个类就完成了token的构造,然后我们需要用它来代替spring-security中的session-cookie机制。首先需要将spring-security的session关闭,实质上我的理解是,token是一个虚拟的session,每次建立连接时,spring-security将它解析出来把它作为认证信息放到Holder里。
关闭session,在securityConfig
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 设置无session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
然后需要写一个Filter,在spring-security进行用户名-密码验证前抢先发生,对token进行验证,若token合法就放入认证信息,就完成了安全认证;若token不合法直接失败。
先配置这个Filter到config中
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 拦截登录请求
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
然后实现这个Filter
/**
* 验证token是否正确,并从token中还原"session"信息
*/
public class JwtAuthenticationFilter extends GenericFilter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String token = request.getHeader(JWTUtils.HEADER_TOKEN_NAME); // 从请求头中拿到token
if (Objects.nonNull(token) && token.trim().length() > 0) {
String payload = JWTUtils.testJwt(token); // 从token中拿到payload
if (Objects.nonNull(payload) && payload.trim().length() > 0) {
ObjectMapper objectMapper = new ObjectMapper();
// 我这个项目的payload是UserDetailImp的序列化后的Json,这里将其还原为UserDetailImpl对象
UserDetailImpl user = objectMapper.readValue(payload, UserDetailImpl.class);
// 将还原得到的认证信息交给spring-security管理(用户信息,认证,用户角色表)
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities()));
}
}
filterChain.doFilter(servletRequest,servletResponse);
}
}
以上,就完成了spring-security使用JWT的全部过程。可以测试使用了
为了方便测试,我还提供了SuccessHandle、FailureHandle、AccessDeniedHandlerImpl用于spring-security登录成功、登录失败、没有认证信息的处理器,其中,SuccessHandle在登录成功后返回当前认证信息的token,拿这个token放到请求头访问接口时,即可自动完成认证。
测试
提供了一个hello的Controller层接口
@RestController
public class hello {
// 拥有ADMIN角色才可以访问
@PreAuthorize("hasAnyRole('ADMIN')")
@RequestMapping("/hello")
String test() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal() instanceof UserDetailImpl) {
UserDetailImpl user = (UserDetailImpl) authentication.getPrincipal();
System.out.println(user.getUsername());
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
System.out.println(authority.getAuthority());
}
}
return "hello";
}
}
直接访问该接口,由于没有认证,会跳转到/login接口下

以admin-123登录后跳转成功页面并得到token

复制token,放到postman的header里,然后再次请求/hello

发现成功得到响应

到控制台看看,得到用户名和角色名

换user-123角色的token登录看看


还可以更换root角色,不再赘述。
spring-security-结合JWT的简单demo的更多相关文章
- 用Spring Security, JWT, Vue实现一个前后端分离无状态认证Demo
简介 完整代码 https://github.com/PuZhiweizuishuai/SpringSecurity-JWT-Vue-Deom 运行展示 后端 主要展示 Spring Security ...
- Spring Security整合JWT,实现单点登录,So Easy~!
前面整理过一篇 SpringBoot Security前后端分离,登录退出等返回json数据,也就是用Spring Security,基于SpringBoot2.1.4 RELEASE前后端分离的情况 ...
- Spring Security和 JWT两大利器来打造一个简易的权限系统。
写在前面 关于 Spring Security Web系统的认证和权限模块也算是一个系统的基础设施了,几乎任何的互联网服务都会涉及到这方面的要求.在Java EE领域,成熟的安全框架解决方案一般有 A ...
- 基于Spring Security和 JWT的权限系统设计
写在前面 关于 Spring Security Web系统的认证和权限模块也算是一个系统的基础设施了,几乎任何的互联网服务都会涉及到这方面的要求.在Java EE领域,成熟的安全框架解决方案一般有 A ...
- Spring Security + OAuth2 + JWT 基本使用
Spring Security + OAuth2 + JWT 基本使用 前面学习了 Spring Security 入门,现在搭配 oauth2 + JWT 进行测试. 1.什么是 OAuth2 OA ...
- 【Spring Cloud & Alibaba 实战 | 总结篇】Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权
一. 前言 hi,大家好~ 好久没更文了,期间主要致力于项目的功能升级和问题修复中,经过一年时间的打磨,[有来]终于迎来v2.0版本,相较于v1.x版本主要完善了OAuth2认证授权.鉴权的逻辑,结合 ...
- Spring Security 整合freemaker 实现简单登录和角色控制
Spring Security 整合freemaker 实现简单登录和角色控制 写这篇文章是因为我做了一个电商网站项目,近期刚加上权限控制.整个过程很简单,在此给大家梳理一下,也算是自己对知识 ...
- spring boot rest 接口集成 spring security(2) - JWT配置
Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...
- Springboot集成Spring Security实现JWT认证
我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 简介 Spring Security作为成熟且强大的安全框架,得到许多大厂的青睐.而作为前后端分离的SSO方案,JWT ...
- Springboot WebFlux集成Spring Security实现JWT认证
我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 简介 在之前的文章<Springboot集成Spring Security实现JWT认证>讲解了如何在传统 ...
随机推荐
- 关于Vue的那些事儿
Vue 渐进式框架 众前端周知,Vue是一套用于构建用户界面的渐进式框架,自底向上逐层应用,关注视图层.那我们就来说道说道: 渐进式:声明式渲染->组件系统->客户端路由(router)- ...
- Kubernetes中资源清单与Pod的生命周期(二)
一.资源清单 1,定义: 在k8s中一般使用yaml格式的文件来创建符合我们预期的资源,这样的yaml被称为资源清单. 使用资源清单创建Pod: kubectl apply -f nginx.yaml ...
- 从IL角度彻底理解回调_委托_指针
从IL角度彻底理解回调_委托_指针 目录 从IL角度彻底理解回调_委托_指针 1.创作此文的背景 1.1.委托能帮助代码更好地封装 1.2.委托能随时随地更方便地运行其他类中的方法 1.3.委托非常适 ...
- Burp Suite抓包使用步骤
Burp Suite抓包工具的操作步骤见安装步骤那篇博客 检查是否存在漏洞,就看拦截之后修改过的数据是否写进了数据库 举例一.上传文件 1.打开Burp.调整Proxy-Intercept-Inter ...
- P1295 [TJOI2011]书架 线段树优化dp,单调栈
P1295 [TJOI2011]书架 本题思路比较好想(对我来说不是),但代码细节很多,奈何洛谷的题解只有思路,然后就是 没有丝毫解释的代码,让人看起来很头疼(~~ 尤其是像我这样的蒟蒻~~),所以便 ...
- Linux实战(3):升级最新内核
# 先查看一下当前内核版本 uname -r # 升级内核 rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch. ...
- ASP.NET Core新书终于上市,完成今年一个目标,赠书活动
2018年.NET Core 2.0发布后,开始逐步学习.NET Core 并逐步在新的项目中使用ASP.NET Core.并且零零散散写的写了将近30篇学习笔记发到园子里,包括ASP.NET Cor ...
- vue中配置axios.js文件,发送请求
为了统一管理请求,每个项目都会去配置axios:而不是在vue中直接使用,那样不好维护等等 下面是我配置的最基础的axios文件 第一步:首先新建一个axios文件,我是放在router文件下的 im ...
- 在Winform程序中使用Spire.Pdf实现页面添加印章处理
在一些场合,我们往往需要使用印章来给每页文档加盖一个印章,以表示该文档经过某个部门的认证的,常规的做法就是打印文档后盖章,如果需要电子档再行扫描一下.这样的的处理,如果文档很多,且仅仅需要电子文档的就 ...
- CVE-2020-0796(Windows SMBv3) RCE漏洞复现
CVE-2020-0796 攻击机:win10:192.168.205.1 靶机win10:192.168.205.132 关闭defender防火墙 0x01 影响版本 Windows 10 190 ...