Spring Boot 2 + Spring Security 5 + JWT 的 Restful简易教程!

本文详细描述了SpringBoot项目中如何使用JWT进行用户登录鉴权,包括JwtAuthenticationFilter的使用、AuthenticationManager的调用过程以及数据库存储和验证机制。还介绍了pom.xml中的依赖配置和基础准备工作,如UserEntity、ResponseEntity和数据库模拟等。
摘要由CSDN通过智能技术生成
  1. POST 用户名密码到 \login

  2. 请求到达 JwtAuthenticationFilter 中的 attemptAuthentication() 方法,获取 request 中的 POST 参数,包装成一个 UsernamePasswordAuthenticationToken 交付给 AuthenticationManagerauthenticate() 方法进行鉴权。

  3. AuthenticationManager 会从 CachingUserDetailsService 中查找用户信息,并且判断账号密码是否正确。

  4. 如果账号密码正确跳转到 JwtAuthenticationFilter 中的 successfulAuthentication() 方法,我们进行签名,生成 token 返回给用户。

  5. 账号密码错误则跳转到 JwtAuthenticationFilter 中的 unsuccessfulAuthentication() 方法,我们返回错误信息让用户重新登入。

请求鉴权:

请求鉴权的主要思路是我们会从请求中的 Authorization 字段拿取 token,如果不存在此字段的用户,Spring Security 会默认会用 AnonymousAuthenticationToken() 包装它,即代表匿名用户。

  1. 任意请求发起

  2. 到达 JwtAuthorizationFilter 中的 doFilterInternal() 方法,进行鉴权。

  3. 如果鉴权成功我们把生成的 AuthenticationSecurityContextHolder.getContext().setAuthentication() 放入 Security,即代表鉴权完成。此处如何鉴权由我们自己代码编写,后序会详细说明。

准备 pom.xml


<?xml version="1.0" encoding="UTF-8"?>

<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.org/xsd/maven-4.0.0.xsd”>

4.0.0

org.springframework.boot

spring-boot-starter-parent

2.1.7.RELEASE

 

org.inlighting

spring-boot-security-jwt

0.0.1-SNAPSHOT

spring-boot-security-jwt

Demo project for Spring Boot

<java.version>1.8</java.version>

org.springframework.boot

spring-boot-starter-security

org.springframework.boot

spring-boot-starter-web

com.auth0

java-jwt

3.8.2

org.springframework.boot

spring-boot-starter-cache

org.ehcache

ehcache

javax.cache

cache-api

org.springframework.boot

spring-boot-starter-test

test

org.springframework.security

spring-security-test

test

javax.xml.bind

jaxb-api

2.3.0

com.sun.xml.bind

jaxb-impl

2.3.0

com.sun.xml.bind

jaxb-core

2.3.0

javax.activation

activation

1.1.1

org.springframework.boot

spring-boot-maven-plugin

pom.xml 配置文件这块没有什么好说的,主要说明下面的几个依赖:

javax.xml.bind

jaxb-api

2.3.0

com.sun.xml.bind

jaxb-impl

2.3.0

com.sun.xml.bind

jaxb-core

2.3.0

javax.activation

activation

1.1.1

因为 ehcache 读取 xml 配置文件时使用了这几个依赖,而这几个依赖从 JDK 9 开始时是选配模块,所以高版本的用户需要添加这几个依赖才能正常使用。

基础工作准备


接下来准备下几个基础工作,就是新建个实体、模拟个数据库,写个 JWT 工具类这种基础操作。

UserEntity.java

关于 role 为什么使用 GrantedAuthority 说明下:其实是为了简化代码,直接用了 Security 现成的 role 类,实际项目中我们肯定要自己进行处理,将其转换为 Security 的 role 类。

public class UserEntity {

public UserEntity(String username, String password, Collection<? extends GrantedAuthority> role) {

this.username = username;

this.password = password;

this.role = role;

}

private String username;

private String password;

private Collection<? extends GrantedAuthority> role;

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public Collection<? extends GrantedAuthority> getRole() {

return role;

}

public void setRole(Collection<? extends GrantedAuthority> role) {

this.role = role;

}

}

ResponseEntity.java

前后端分离为了方便前端我们要统一 json 的返回格式,所以自定义一个 ResponseEntity.java。

public class ResponseEntity {

public ResponseEntity() {

}

public ResponseEntity(int status, String msg, Object data) {

this.status = status;

this.msg = msg;

this.data = data;

}

private int status;

private String msg;

private Object data;

public int getStatus() {

return status;

}

public void setStatus(int status) {

this.status = status;

}

public String getMsg() {

return msg;

}

public void setMsg(String msg) {

this.msg = msg;

}

public Object getData() {

return data;

}

public void setData(Object data) {

this.data = data;

}

}

Database.java

这里我们使用一个 HashMap 模拟了一个数据库,密码我已经预先用 Bcrypt 加密过了,这也是 Spring Security 官方推荐的加密算法(MD5 加密已经在 Spring Security 5 中被移除了,不安全)。

| 用户名 | 密码 | 权限 |

| — | — | — |

| jack | jack123 存 Bcrypt 加密后 | ROLE_USER |

| danny | danny123 存 Bcrypt 加密后 | ROLE_EDITOR |

| smith | smith123 存 Bcrypt 加密后 | ROLE_ADMIN |

@Component

public class Database {

private Map<String, UserEntity> data = null;

public Map<String, UserEntity> getDatabase() {

if (data == null) {

data = new HashMap<>();

UserEntity jack = new UserEntity(

“jack”,

“$2a 10 10 10AQol1A.LkxoJ5dEzS5o5E.QG9jD.hncoeCGdVaMQZaiYZ98V/JyRq”,

getGrants(“ROLE_USER”));

UserEntity danny = new UserEntity(

“danny”,

“$2a$10$8nMJR6r7lvh9H2INtM2vtuA156dHTcQUyU.2Q2OK/7LwMd/I.HM12”,

getGrants(“ROLE_EDITOR”));

UserEntity smith = new UserEntity(

“smith”,

“$2a 10 10 10E86mKigOx1NeIr7D6CJM3OQnWdaPXOjWe4OoRqDqFgNgowvJW9nAi”,

getGrants(“ROLE_ADMIN”));

data.put(“jack”, jack);

data.put(“danny”, danny);

data.put(“smith”, smith);

}

return data;

}

private Collection getGrants(String role) {

return AuthorityUtils.commaSeparatedStringToAuthorityList(role);

}

}

UserService.java

这里再模拟一个 service,主要就是模仿数据库的操作。

@Service

public class UserService {

@Autowired

private Database database;

public UserEntity getUserByUsername(String username) {

return database.getDatabase().get(username);

}

}

JwtUtil.java

自己编写的一个工具类,主要负责 JWT 的签名和鉴权。

public class JwtUtil {

// 过期时间5分钟

private final static long EXPIRE_TIME = 5 * 60 * 1000;

/**

* 生成签名,5min后过期

* @param username 用户名

* @param secret 用户的密码

* @return 加密的token

*/

public static String sign(String username, String secret) {

Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);

try {

Algorithm algorithm = Algorithm.HMAC256(secret);

return JWT.create()

.withClaim(“username”, username)

.withExpiresAt(expireDate)

.sign(algorithm);

} catch (Exception e) {

return null;

}

}

/**

* 校验token是否正确

* @param token 密钥

* @param secret 用户的密码

* @return 是否正确

*/

public static boolean verify(String token, String username, String secret) {

try {

Algorithm algorithm = Algorithm.HMAC256(secret);

JWTVerifier verifier = JWT.require(algorithm)

.withClaim(“username”, username)

.build();

DecodedJWT jwt = verifier.verify(token);

return true;

} catch (Exception e) {

return false;

}

}

/**

* 获得token中的信息无需secret解密也能获得

* @return token中包含的用户名

*/

public static String getUsername(String token) {

try {

DecodedJWT jwt = JWT.decode(token);

return jwt.getClaim(“username”).asString();

} catch (JWTDecodeException e) {

return null;

}

}

}

Spring Security 改造


登入这块,我们使用自定义的 JwtAuthenticationFilter 来进行登入。

请求鉴权,我们使用自定义的 JwtAuthorizationFilter 来处理。

也许大家觉得两个单词长的有点像,???。

UserDetailsServiceImpl.java

我们首先实现官方的 UserDetailsService 接口,这里主要负责一个从数据库拿数据的操作。

@Service

public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired

private UserService userService;

@Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

UserEntity userEntity = userService.getUserByUsername(username);

if (userEntity == null) {

throw new UsernameNotFoundException(“This username didn’t exist.”);

}

return new User(userEntity.getUsername(), userEntity.getPassword(), userEntity.getRole());

}

}

后序我们还需要对其进行缓存改造,不然每次请求都要从数据库拿一次数据鉴权,对数据库压力太大了。

JwtAuthenticationFilter.java

这个过滤器主要处理登入操作,我们继承了 UsernamePasswordAuthenticationFilter,这样能大大简化我们的工作量。

public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

/*

过滤器一定要设置 AuthenticationManager,所以此处我们这么编写,这里的 AuthenticationManager

我会从 Security 配置的时候传入

*/

public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {

/*

运行父类 UsernamePasswordAuthenticationFilter 的构造方法,能够设置此滤器指定

方法为 POST [\login]

*/

super();

setAuthenticationManager(authenticationManager);

}

@Override

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

// 从请求的 POST 中拿取 username 和 password 两个字段进行登入

String username = request.getParameter(“username”);

String password = request.getParameter(“password”);

UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);

// 设置一些客户 IP 啥信息,后面想用的话可以用,虽然没啥用

setDetails(request, token);

// 交给 AuthenticationManager 进行鉴权
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

做任何事情都要用心,要非常关注细节。看起来不起眼的、繁琐的工作做透了会有意想不到的价值。
当然要想成为一个技术大牛也需要一定的思想格局,思想决定未来你要往哪个方向去走, 建议多看一些人生规划方面的书籍,多学习名人的思想格局,未来你的路会走的更远。

更多的技术点思维导图我已经做了一个整理,涵盖了当下互联网最流行99%的技术点,在这里我将这份导图分享出来,以及为金九银十准备的一整套面试体系,上到集合,下到分布式微服务

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
负担。**[外链图片转存中…(img-xBPpT4nD-1711814014095)]

[外链图片转存中…(img-9lp08QqV-1711814014095)]

[外链图片转存中…(img-oRSxd18e-1711814014096)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

做任何事情都要用心,要非常关注细节。看起来不起眼的、繁琐的工作做透了会有意想不到的价值。
当然要想成为一个技术大牛也需要一定的思想格局,思想决定未来你要往哪个方向去走, 建议多看一些人生规划方面的书籍,多学习名人的思想格局,未来你的路会走的更远。

更多的技术点思维导图我已经做了一个整理,涵盖了当下互联网最流行99%的技术点,在这里我将这份导图分享出来,以及为金九银十准备的一整套面试体系,上到集合,下到分布式微服务

[外链图片转存中…(img-2ofqQ2Na-1711814014096)]

[外链图片转存中…(img-xa3LaTwy-1711814014096)]

[外链图片转存中…(img-OsYCVCON-1711814014097)]

[外链图片转存中…(img-NBXmkPQB-1711814014097)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

  • 23
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值