本文不着重讲述springsecurity相关概念及其原理,而是致力于总结一些实战应用场景
基于数据库的登录认证
1.创建数据库表
-- 创建数据库
create database `security-demo`;
use `security-demo`;
-- 创建用户表
create table `user`(
`id` int not null auto_increment primary key,
`username` varchar(50) default null ,
`password` varchar(500) default null,
`enabled` boolean not null
);
-- 唯一索引
create unique index `user_username_uindex` on `user`(`username`);
-- 插入用户数据(密码是 "abc" )
insert into `user` (`username`, `password`, `enabled`) values
('admin', '{bcrypt}$2a$10$grldnijsqmuvl/au9ofl.edwmoohzzs7.rmnsjz.0fxo/btk76klw', true),
('helen', '{bcrypt}$2a$10$grldnijsqmuvl/au9ofl.edwmoohzzs7.rmnsjz.0fxo/btk76klw', true),
('tom', '{bcrypt}$2a$10$grldnijsqmuvl/au9ofl.edwmoohzzs7.rmnsjz.0fxo/btk76klw', true);2.创建实体类
@data
public class user {
@tableid(value = "id", type = idtype.auto)
private integer id;
private string username;
private string password;
private boolean enabled;
}3.定义dbuserdetailsmanager
dbuserdetailsmanager要实现userdetailsmanager和userdetailspasswordservice并重写其抽象方法。
@component
public class dbuserdetailsmanager implements userdetailsmanager, userdetailspasswordservice {
@autowired
private userservice userservice;
@override
public userdetails updatepassword(userdetails user, string newpassword) {
return null;
}
/**
* 根据用户名加载用户详情
* @param username 用户名
* @return 用户详情
* @throws usernamenotfoundexception 如果用户不存在
*/
@override
public userdetails loaduserbyusername(string username) throws usernamenotfoundexception {
user user = userservice.lambdaquery().eq(user::getusername, username).one();
if (user == null) {
throw new usernamenotfoundexception("用户不存在");
}
// 返回自定义的安全用户对象,将数据库实体包装在内
return new securityuser(user);
}
@override
public void createuser(userdetails user) {
}
@override
public void updateuser(userdetails user) {
}
@override
public void deleteuser(string username) {
}
@override
public void changepassword(string oldpassword, string newpassword) {
}
@override
public boolean userexists(string username) {
return false;
}
}4.编辑配置
配置要根据项目实际情况进行选择,如果不主动实现filterchain,框架也会给出一个默认实现
@configuration
@enablemethodsecurity // 开启方法级权限控制
public class websecurityconfig {
@bean
public passwordencoder passwordencoder() {
return new bcryptpasswordencoder();
}
@bean
public securityfilterchain filterchain(httpsecurity http) throws exception {
http
// 1. 配置请求授权
.authorizehttprequests(authorize -> authorize
.requestmatchers("/user/add", "/login", "/error").permitall() // 明确放行注册、登录和错误页
.requestmatchers("/static/**", "/css/**", "/js/**").permitall() // 放行静态资源
.anyrequest().authenticated() // 剩下的所有请求都需要登录
)
// 2. 自定义表单登录逻辑
.formlogin(form -> form
.defaultsuccessurl("/", true) // 登录成功后强制跳转到首页
.permitall()
)
// 3. 配置注销功能
.logout(logout -> logout
.logouturl("/logout") // 注销接口地址
.logoutsuccessurl("/login?logout") // 注销成功后跳转回登录页并带上提示
.invalidatehttpsession(true) // 销毁 session .deletecookies("jsessionid") // 删除 cookie .permitall()
)
// 4. 防护配置
.csrf(csrf -> csrf.disable()); // demo 阶段保持禁用以便 api 测试
return http.build();
}
}密码加密
1.配置passwordencoder
config中添加passwordencoder,指明bcrypt加密算法
@bean
public passwordencoder passwordencoder() {
return new bcryptpasswordencoder();
}
2.加密密码
passwordencoder.encode(user.getpassword())
添加用户
1.自定义securityuser 类
自定义一个 securityuser 类作为包装类既能保持实体类 user 的纯粹性(不依赖 spring security 框架),也能在安全上下文中携带完整的业务数据。
public class securityuser implements userdetails {
@getter
private final user user; // 持有原始的数据库实体对象
public securityuser(user user) {
this.user = user;
}
@override
public string getusername() {
return user.getusername();
}
@override
public string getpassword() {
return user.getpassword();
}
@override
public collection<? extends grantedauthority> getauthorities() {
// 后续可以在此根据 user 角色添加权限
return new arraylist<>();
}
@override
public boolean isaccountnonexpired() { return true; }
@override
public boolean isaccountnonlocked() { return true; }
@override
public boolean iscredentialsnonexpired() { return true; }
@override
public boolean isenabled() {
return user.getenabled() != null && user.getenabled();
}
}2.重写dbuserdetailsmanager中方法
@override
public void createuser(userdetails userdetails) {
if (userdetails instanceof securityuser) {
// 如果是自定义类型,直接获取内部持有实体保存
userservice.save(((securityuser) userdetails).getuser());
} else {
// 兜底逻辑:处理标准的 userdetails 对象
user user = new user();
user.setusername(userdetails.getusername());
user.setpassword(userdetails.getpassword());
user.setenabled(userdetails.isenabled());
userservice.save(user);
}
}
@override
public void updateuser(userdetails user) {
// 根据业务需求实现,例如:
// userservice.updatebyid(converttoentity(user));
}
@override
public void deleteuser(string username) {
userservice.lambdaupdate().eq(user::getusername, username).remove();
}
@override
public void changepassword(string oldpassword, string newpassword) {
}
@override
public boolean userexists(string username) {
return userservice.lambdaquery().eq(user::getusername, username).exists();
}3.usercontroller中实现接口并调用已重写的方法
@requiredargsconstructor
@restcontroller
@requestmapping("/user")
public class usercontroller {
private final userservice userservice;
private final userdetailsmanager userdetailsmanager;
private final passwordencoder passwordencoder;
@postmapping("/add")
public string add(@requestbody user user) {
// 1. 校验用户是否存在
if (userdetailsmanager.userexists(user.getusername())) {
return "添加失败,用户名已存在";
}
// 2. 加密密码
user.setpassword(passwordencoder.encode(user.getpassword()));
// 3. 使用自定义的 securityuser 包装实体类
securityuser securityuser = new securityuser(user);
// 4. 通过标准接口保存用户
try {
userdetailsmanager.createuser(securityuser);
return "添加成功";
} catch (exception e) {
log.error("添加用户失败", e);
return "添加失败:" + e.getmessage();
}
}
}前后端响应
在用户登录成功,失败或是注销时返回给前端对应的json数据(此处为了方便演示,把统一响应结果封装在map集合中,实际开发还是以自定义结果对象为好)
登录成功
编写实现了authenticationsuccesshandler的类,用户登录成功时会调用该类中的onauthenticationsuccess方法
@component
public class myauthenticationsuccesshandler implements authenticationsuccesshandler {
@override
public void onauthenticationsuccess(httpservletrequest request, httpservletresponse response, authentication authentication) throws ioexception, servletexception {
//获取用户身份信息
object principal = authentication.getprincipal();
//创建结果对象
hashmap result = new hashmap();
result.put("code", 0);
result.put("message", "登录成功");
result.put("data", principal);
//转换成json字符串
string json = json.tojsonstring(result);
//返回响应
response.setcontenttype("application/json;charset=utf-8");
response.getwriter().println(json);
}
}登陆失败
编写实现了authenticationfailurehandler的类,用户登录失败时会调用该类中的onauthenticationfailure方法
@component
public class myauthenticationfailurehandler implements authenticationfailurehandler {
@override
public void onauthenticationfailure(httpservletrequest request, httpservletresponse response, authenticationexception exception) throws ioexception, servletexception {
//获取错误信息
string localizedmessage = exception.getlocalizedmessage();
//创建结果对象
hashmap result = new hashmap();
result.put("code", -1);
result.put("message", localizedmessage);
//转换成json字符串
string json = json.tojsonstring(result);
//返回响应
response.setcontenttype("application/json;charset=utf-8");
response.getwriter().println(json);
}
}注销
编写实现了logoutsuccesshandler的类,用户注销时会调用该类中的onlogoutsuccess方法
@component
public class mylogoutsuccesshandler implements logoutsuccesshandler {
@override
public void onlogoutsuccess(httpservletrequest request, httpservletresponse response, authentication authentication) throws ioexception, servletexception {
//创建结果对象
hashmap result = new hashmap();
result.put("code", 0);
result.put("message", "注销成功");
//转换成json字符串
string json = json.tojsonstring(result);
//返回响应
response.setcontenttype("application/json;charset=utf-8");
response.getwriter().println(json);
}
}挂载
目前位置以上三个handler只是进行了实现,但是为被挂载使用,挂载方法:
@configuration
@enablemethodsecurity // 开启方法级权限控制
public class websecurityconfig {
private final myauthenticationsuccesshandler successhandler;
private final mylogoutsuccesshandler logoutsuccesshandler;
private final myauthenticationfailurehandler failurehandler;
// 构造器注入
public websecurityconfig(myauthenticationsuccesshandler successhandler, mylogoutsuccesshandler logoutsuccesshandler, myauthenticationfailurehandler failurehandler) {
this.successhandler = successhandler;
this.logoutsuccesshandler = logoutsuccesshandler;
this.failurehandler = failurehandler;
}
@bean
public securityfilterchain filterchain(httpsecurity http) throws exception {
http.formlogin(form -> form
.successhandler(successhandler) // 启用成功处理器
.failurehandler(failurehandler) // 启用失败处理器(
);
http.logout(logout -> logout
.logoutsuccesshandler(logoutsuccesshandler) // 启用注销处理器
.invalidatehttpsession(true) // 销毁 session
);
return http.build();
}
}先通过构造器注入handler,再在http构造时对应的lambda表达式处挂载。
跨域
跨域全称是跨域资源共享(cross-origin resources sharing,cors),它是浏览器的保护机制,只允许网页请求统一域名下的服务,同一域名指=>协议、域名、端口号都要保持一致,如果有一项不同,那么就是跨域请求。在前后端分离的项目中,需要解决跨域的问题。
在springsecurity中解决跨域很简单,在配置文件中添加如下配置即可
//跨域 http.cors(withdefaults());
身份认证
基本概念

在spring security框架中,securitycontextholder、securitycontext、authentication、principal和credential是一些与身份验证和授权相关的重要概念。它们之间的关系如下:
- securitycontextholder:securitycontextholder 是 spring security 存储已认证用户详细信息的地方。
- securitycontext:securitycontext 是从 securitycontextholder 获取的内容,包含当前已认证用户的 authentication 信息。
- authentication:authentication 表示用户的身份认证信息。它包含了用户的principal、credential和authority信息。
- principal:表示用户的身份标识。它通常是一个表示用户的实体对象,例如用户名。principal可以通过authentication对象的getprincipal()方法获取。
- credentials:表示用户的凭证信息,例如密码、证书或其他认证凭据。credential可以通过authentication对象的getcredentials()方法获取。
- grantedauthority:表示用户被授予的权限
总结起来,securitycontextholder用于管理当前线程的安全上下文,存储已认证用户的详细信息,其中包含了securitycontext对象,该对象包含了authentication对象,后者表示用户的身份验证信息,包括principal(用户的身份标识)和credential(用户的凭证信息)。
在controller中获取用户信息
indexcontroller:
@restcontroller
public class indexcontroller {
/**
* 获取当前登录用户的详细信息
* 用于学习 securitycontextholder 相关 api
*/ @getmapping("/user/info")
public string getuserinfo() {
// 1. 从 securitycontextholder 中获取认证对象
// securitycontextholder 是一个工具类,内部通过 threadlocal 存储了当前线程的认证信息
authentication authentication = securitycontextholder.getcontext().getauthentication();
// 2. 获取用户身份主体 (在你的项目中,你自定义的 securityuser )
object principal = authentication.getprincipal();
// 3. 获取用户权限信息 (如角色 role_admin 等,均为自定义)
object authorities = authentication.getauthorities();
// 4. 获取认证状态 (是否已登录)
boolean isauthenticated = authentication.isauthenticated();
// 为了方便查看,我们将这些信息封装到 map 中返回
map<string, object> info = new java.util.hashmap<>();
info.put("principal", principal);
info.put("authorities", authorities);
info.put("isauthenticated", isauthenticated);
info.put("name", authentication.getname()); // 获取用户名
// 使用 fastjson2 手动转换为 json 字符串
return json.tojsonstring(info);
}
}会话并发处理
简单来说就是,后登录的账号会使先登录的账号失效。
实现处理器接口sessioninformationexpiredstrategy
@component
public class mysessioninformationexpiredstrategy implements sessioninformationexpiredstrategy {
@override
public void onexpiredsessiondetected(sessioninformationexpiredevent event) throws ioexception, servletexception {
// 1. 获取响应对象
httpservletresponse response = event.getresponse();
// 2. 创建你要返回的 json 结果
map<string, object> result = new hashmap<>();
result.put("code", 401);
result.put("message", "您的账号已在其他地方登录,当前会话已失效");
// 3. 将结果转换为 json 并写入响应
response.setcontenttype("application/json;charset=utf-8");
response.getwriter().println(json.tojsonstring(result));
}
}config中挂载配置
http.sessionmanagement(session -> session
.maximumsessions(1) // 限制同一个账号只能 1 个登录
.expiredsessionstrategy(sessioninformationexpiredstrategy) // 挂载自定义策略
);
到此这篇关于springsecurity入门实战应用场景的文章就介绍到这了,更多相关springsecurity登录认证内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论