基于数据库 + jwt 的 spring boot security 完整示例
该示例实现 用户数据库存储 + jwt 无状态认证 + 角色权限控制,适用于前后端分离架构。
一、 技术依赖
在 pom.xml 中添加核心依赖:
<!-- spring boot security -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-security</artifactid>
</dependency>
<!-- spring boot web -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<!-- spring boot data jpa -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-data-jpa</artifactid>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupid>com.mysql</groupid>
<artifactid>mysql-connector-j</artifactid>
<scope>runtime</scope>
</dependency>
<!-- jwt 依赖 -->
<dependency>
<groupid>io.jsonwebtoken</groupid>
<artifactid>jjwt-api</artifactid>
<version>0.11.5</version>
</dependency>
<dependency>
<groupid>io.jsonwebtoken</groupid>
<artifactid>jjwt-impl</artifactid>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupid>io.jsonwebtoken</groupid>
<artifactid>jjwt-jackson</artifactid>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- lombok (简化代码) -->
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<optional>true</optional>
</dependency>二、 数据库设计
设计 sys_user(用户表)、sys_role(角色表)、sys_user_role(用户角色关联表),执行 sql:
create database if not exists security_jwt;
use security_jwt;
-- 用户表
create table sys_user (
id bigint primary key auto_increment,
username varchar(50) not null unique,
password varchar(100) not null,
status tinyint default 1 comment '1-正常 0-禁用',
create_time datetime default current_timestamp
);
-- 角色表
create table sys_role (
id bigint primary key auto_increment,
role_name varchar(50) not null,
role_code varchar(50) not null unique
);
-- 用户角色关联表
create table sys_user_role (
id bigint primary key auto_increment,
user_id bigint not null,
role_id bigint not null,
foreign key (user_id) references sys_user(id),
foreign key (role_id) references sys_role(id)
);
-- 插入测试数据
insert into sys_role (role_name, role_code) values ('管理员', 'role_admin'), ('普通用户', 'role_user');
-- 密码是 123456 (bcrypt 加密后)
insert into sys_user (username, password, status) values
('admin', '$2a$10$e0wfw5akz4wlz7xqxxyyz9z8a7b6c5d4e3f2g1h0', 1),
('user', '$2a$10$e0wfw5akz4wlz7xqxxyyz9z8a7b6c5d4e3f2g1h0', 1);
insert into sys_user_role (user_id, role_id) values (1,1), (2,2);三、 核心配置与代码
1. 配置文件 application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/security_jwt?useunicode=true&characterencoding=utf8&servertimezone=asia/shanghai
username: root
password: 你的数据库密码
driver-class-name: com.mysql.cj.jdbc.driver
jpa:
hibernate:
ddl-auto: none
show-sql: true
properties:
hibernate:
format_sql: true
jwt:
secret: 7b3a9f8d2e4c6a1b5f9e3c8a2d7b1f4e6c9d8b2a5e7f3c1d4b6a8f9e2c3d5b7a
expiration: 3600000 # token有效期 1小时2. 实体类
- 用户实体 sysuser.java
package com.example.demo.entity;
import lombok.data;
import javax.persistence.*;
import java.util.list;
@data
@entity
@table(name = "sys_user")
public class sysuser {
@id
@generatedvalue(strategy = generationtype.identity)
private long id;
private string username;
private string password;
private integer status;
@manytomany(fetch = fetchtype.eager)
@jointable(
name = "sys_user_role",
joincolumns = @joincolumn(name = "user_id"),
inversejoincolumns = @joincolumn(name = "role_id")
)
private list<sysrole> roles;
}- 角色实体 sysrole.java
package com.example.demo.entity;
import lombok.data;
import javax.persistence.*;
@data
@entity
@table(name = "sys_role")
public class sysrole {
@id
@generatedvalue(strategy = generationtype.identity)
private long id;
private string rolename;
private string rolecode;
}3. jwt 工具类 jwtutil.java
package com.example.demo.util;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.value;
import org.springframework.security.core.userdetails.userdetails;
import org.springframework.stereotype.component;
import java.util.date;
import java.util.hashmap;
import java.util.map;
@component
public class jwtutil {
@value("${jwt.secret}")
private string secret;
@value("${jwt.expiration}")
private long expiration;
// 生成token
public string generatetoken(userdetails userdetails) {
map<string, object> claims = new hashmap<>();
return jwts.builder()
.setclaims(claims)
.setsubject(userdetails.getusername())
.setissuedat(new date())
.setexpiration(new date(system.currenttimemillis() + expiration))
.signwith(signaturealgorithm.hs512, secret)
.compact();
}
// 验证token
public boolean validatetoken(string token, userdetails userdetails) {
string username = getusernamefromtoken(token);
return username.equals(userdetails.getusername()) && !istokenexpired(token);
}
// 从token获取用户名
public string getusernamefromtoken(string token) {
return getclaimsfromtoken(token).getsubject();
}
// 解析token
private claims getclaimsfromtoken(string token) {
return jwts.parser()
.setsigningkey(secret)
.parseclaimsjws(token)
.getbody();
}
// 判断token是否过期
private boolean istokenexpired(string token) {
date expiration = getclaimsfromtoken(token).getexpiration();
return expiration.before(new date());
}
}4. 自定义 userdetailsservice
package com.example.demo.service;
import com.example.demo.entity.sysrole;
import com.example.demo.entity.sysuser;
import com.example.demo.repository.sysuserrepository;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.security.core.authority.simplegrantedauthority;
import org.springframework.security.core.userdetails.user;
import org.springframework.security.core.userdetails.userdetails;
import org.springframework.security.core.userdetails.userdetailsservice;
import org.springframework.security.core.userdetails.usernamenotfoundexception;
import org.springframework.stereotype.service;
import java.util.list;
import java.util.stream.collectors;
@service
public class userdetailsserviceimpl implements userdetailsservice {
@autowired
private sysuserrepository sysuserrepository;
@override
public userdetails loaduserbyusername(string username) throws usernamenotfoundexception {
sysuser sysuser = sysuserrepository.findbyusername(username)
.orelsethrow(() -> new usernamenotfoundexception("用户不存在"));
// 转换角色为权限
list<simplegrantedauthority> authorities = sysuser.getroles().stream()
.map(role -> new simplegrantedauthority(role.getrolecode()))
.collect(collectors.tolist());
return new user(sysuser.getusername(), sysuser.getpassword(), authorities);
}
}5. jwt 认证过滤器 jwtauthenticationfilter.java
package com.example.demo.filter;
import com.example.demo.service.userdetailsserviceimpl;
import com.example.demo.util.jwtutil;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.security.authentication.usernamepasswordauthenticationtoken;
import org.springframework.security.core.context.securitycontextholder;
import org.springframework.security.core.userdetails.userdetails;
import org.springframework.security.web.authentication.webauthenticationdetailssource;
import org.springframework.stereotype.component;
import org.springframework.web.filter.onceperrequestfilter;
import javax.servlet.filterchain;
import javax.servlet.servletexception;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import java.io.ioexception;
@component
public class jwtauthenticationfilter extends onceperrequestfilter {
@autowired
private jwtutil jwtutil;
@autowired
private userdetailsserviceimpl userdetailsservice;
@override
protected void dofilterinternal(httpservletrequest request, httpservletresponse response, filterchain chain)
throws servletexception, ioexception {
// 获取token
string token = request.getheader("authorization");
if (token != null && token.startswith("bearer ")) {
token = token.substring(7);
string username = jwtutil.getusernamefromtoken(token);
// 用户名存在且未认证
if (username != null && securitycontextholder.getcontext().getauthentication() == null) {
userdetails userdetails = userdetailsservice.loaduserbyusername(username);
// 验证token有效性
if (jwtutil.validatetoken(token, userdetails)) {
usernamepasswordauthenticationtoken authenticationtoken =
new usernamepasswordauthenticationtoken(userdetails, null, userdetails.getauthorities());
authenticationtoken.setdetails(new webauthenticationdetailssource().builddetails(request));
securitycontextholder.getcontext().setauthentication(authenticationtoken);
}
}
}
chain.dofilter(request, response);
}
}6. security 核心配置 securityconfig.java
package com.example.demo.config;
import com.example.demo.filter.jwtauthenticationfilter;
import com.example.demo.service.userdetailsserviceimpl;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.security.authentication.authenticationmanager;
import org.springframework.security.config.annotation.authentication.builders.authenticationmanagerbuilder;
import org.springframework.security.config.annotation.method.configuration.enableglobalmethodsecurity;
import org.springframework.security.config.annotation.web.builders.httpsecurity;
import org.springframework.security.config.annotation.web.configuration.enablewebsecurity;
import org.springframework.security.config.http.sessioncreationpolicy;
import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder;
import org.springframework.security.crypto.password.passwordencoder;
import org.springframework.security.web.securityfilterchain;
import org.springframework.security.web.authentication.usernamepasswordauthenticationfilter;
@configuration
@enablewebsecurity
@enableglobalmethodsecurity(prepostenabled = true) // 开启方法级权限控制
public class securityconfig {
@autowired
private userdetailsserviceimpl userdetailsservice;
@autowired
private jwtauthenticationfilter jwtauthenticationfilter;
@bean
public passwordencoder passwordencoder() {
return new bcryptpasswordencoder();
}
@bean
public authenticationmanager authenticationmanager(httpsecurity http) throws exception {
return http.getsharedobject(authenticationmanagerbuilder.class)
.userdetailsservice(userdetailsservice)
.passwordencoder(passwordencoder())
.and()
.build();
}
@bean
public securityfilterchain securityfilterchain(httpsecurity http) throws exception {
http
.csrf().disable() // 前后端分离关闭csrf
.sessionmanagement().sessioncreationpolicy(sessioncreationpolicy.stateless) // 无状态
.and()
.authorizehttprequests()
.antmatchers("/auth/login").permitall() // 登录接口公开
.anyrequest().authenticated(); // 其他接口需认证
// 添加jwt过滤器
http.addfilterbefore(jwtauthenticationfilter, usernamepasswordauthenticationfilter.class);
return http.build();
}
}7. 登录控制器与测试接口
package com.example.demo.controller;
import com.example.demo.util.jwtutil;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.security.authentication.authenticationmanager;
import org.springframework.security.authentication.usernamepasswordauthenticationtoken;
import org.springframework.security.core.userdetails.userdetails;
import org.springframework.security.core.userdetails.userdetailsservice;
import org.springframework.web.bind.annotation.*;
@restcontroller
@requestmapping("/auth")
public class authcontroller {
@autowired
private authenticationmanager authenticationmanager;
@autowired
private userdetailsservice userdetailsservice;
@autowired
private jwtutil jwtutil;
// 登录接口
@postmapping("/login")
public string login(@requestparam string username, @requestparam string password) {
// 认证用户
authenticationmanager.authenticate(new usernamepasswordauthenticationtoken(username, password));
// 生成token
userdetails userdetails = userdetailsservice.loaduserbyusername(username);
return jwtutil.generatetoken(userdetails);
}
}
// 测试接口
@restcontroller
@requestmapping("/test")
public class testcontroller {
@getmapping("/admin")
@preauthorize("hasrole('role_admin')")
public string admintest() {
return "管理员接口";
}
@getmapping("/user")
@preauthorize("hasrole('role_user')")
public string usertest() {
return "普通用户接口";
}
}8. 仓库接口 sysuserrepository.java
package com.example.demo.repository;
import com.example.demo.entity.sysuser;
import org.springframework.data.jpa.repository.jparepository;
import java.util.optional;
public interface sysuserrepository extends jparepository<sysuser, long> {
optional<sysuser> findbyusername(string username);
}四、 测试步骤
- 启动项目,使用 postman 调用
post /auth/login?username=admin&password=123456获取 token。 - 调用测试接口时,在请求头添加
authorization: bearer 生成的token。 - 访问
/test/admin需 admin 角色,访问/test/user需 user 角色,无权限或 token 失效会返回 403/401。
到此这篇关于基于数据库 + jwt 的 spring boot security 完整示例代码的文章就介绍到这了,更多相关spring boot security jwt内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论