spring security实现动态路由权限控制
主要步骤如下:
- 1、securityuser implements userdetails 接口中的方法
- 2、自定义认证:userdetailsserviceimpl implements userdetailsservice
- 3、添加登录过滤器loginfilter extends onceperrequestfilter
每次访问接口都会经过此,我们可以在这里记录请求参数、响应内容,或者处理前后端分离情况下, 以token换用户权限信息,token是否过期,请求头类型是否正确,防止非法请求等等
- 4、动态权限过滤器,用于实现基于路径的动态权限过滤:securityfilter extends abstractsecurityinterceptor implements filter
- 5、未登录访问控制类:adminauthenticationentrypoint implements authenticationentrypoint
- 6、获取访问url所需要的角色信息类:urlfilterinvocationsecuritymetadatasource implements filterinvocationsecuritymetadatasource
- 7、权限认证处理类:urlaccessdecisionmanager implements accessdecisionmanager,认证失败抛出:accessdeniedexception 异常
- 8、权限认证失败后的处理类:urlaccessdeniedhandler implements accessdeniedhandler
- 9、核心配置securityconfig

代码实现
1、securityuser implements userdetails 接口中的方法
package com.example.security.url.entity;
import lombok.data;
import lombok.tostring;
import lombok.extern.slf4j.slf4j;
import org.springframework.security.core.grantedauthority;
import org.springframework.security.core.authority.simplegrantedauthority;
import org.springframework.security.core.userdetails.userdetails;
import org.springframework.util.collectionutils;
import java.util.arraylist;
import java.util.collection;
import java.util.list;
/**
* @author deyou kong
* @description security验证用户
* @date 2023/2/9 3:01 下午
*/
@data
@slf4j
@tostring
public class securityuser implements userdetails {
/**
* 用户信息
*/
private user user;
/**
* 用户拥有的角色列表
*/
private list<role> roles;
public securityuser() { }
public securityuser(user user) {
if (user != null) {
this.user = user;
}
}
public securityuser(user user, list<role> rolelist) {
if (user != null) {
this.user = user;
this.roles = rolelist;
}
}
/**
* 获取当前用户所具有的角色
* @return 返回角色列表 list<role.getcode()>
*/
@override
public collection<? extends grantedauthority> getauthorities() {
collection<grantedauthority> authorities = new arraylist<>();
if (!collectionutils.isempty(this.roles)) {
for (role role : this.roles) {
simplegrantedauthority authority = new simplegrantedauthority(role.getcode());
authorities.add(authority);
}
}
return authorities;
}
@override
public string getpassword() {
return user.getpassword();
}
@override
public string getusername() {
return user.getusername();
}
@override
public boolean isaccountnonexpired() {
return true;
}
@override
public boolean isaccountnonlocked() {
return true;
}
@override
public boolean iscredentialsnonexpired() {
return true;
}
@override
public boolean isenabled() {
return user.getstatus() == 1 ? true: false;
}
}
2、自定义认证:userdetailsserviceimpl implements userdetailsservice
package com.example.security.url.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.lambdaquerywrapper;
import com.example.security.url.constants.resultconstant;
import com.example.security.url.dao.rolemapper;
import com.example.security.url.dao.usermapper;
import com.example.security.url.dao.userrolemapper;
import com.example.security.url.entity.*;
import lombok.extern.slf4j.slf4j;
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 org.springframework.util.collectionutils;
import javax.annotation.resource;
import java.util.arraylist;
import java.util.list;
import java.util.set;
import java.util.stream.collectors;
@service
@slf4j
public class userdetailsserviceimpl implements userdetailsservice {
@resource
usermapper usermapper;
@resource
userrolemapper userrolemapper;
@resource
rolemapper rolemapper;
@override
public userdetails loaduserbyusername(string username) throws usernamenotfoundexception {
log.info("userdetailsservice实现类");
lambdaquerywrapper<user> querywrapper = new lambdaquerywrapper<>();
querywrapper.eq(user::getusername, username);
user user = usermapper.selectone(querywrapper);
//如果用户被禁用,则不再查询权限表
if (user == null){
// 抛出异常,会被loginfailhandlerentrypoint捕获
throw new usernamenotfoundexception(resultconstant.user_not_exist);
//return null;
}
return new securityuser(user, getuserroles(user.getid()));
}
/**
* 根据用户id获取角色权限信息
*
* @param userid
* @return
*/
private list<role> getuserroles(integer userid) {
lambdaquerywrapper<userrole> userrolelambdaquerywrapper = new lambdaquerywrapper<>();
userrolelambdaquerywrapper.eq(userrole::getuserid, userid);
list<userrole> userroles = userrolemapper.selectlist(userrolelambdaquerywrapper);
// 判断用户有没有角色,没有角色,直接返回空列表
if (collectionutils.isempty(userroles)){
return new arraylist<>();
}
set<integer> roleidset = userroles.stream().map(userrole::getroleid).collect(collectors.toset());
list<role> roles = rolemapper.selectbatchids(roleidset);
if (collectionutils.isempty(roles)){
return new arraylist<>();
}
return roles;
}
}3、添加登录过滤器loginfilter extends onceperrequestfilter
每次访问接口都会经过此,我们可以在这里记录请求参数、响应内容等日志,或者处理前后端分离情况下,以token换用户权限信息,token是否过期,请求头类型是否正确,防止非法请求等等
package com.example.security.url.filter;
import com.example.security.url.common.result.commonresult;
import com.example.security.url.exception.loginexception;
import com.example.security.url.property.ignoreurlsconfig;
import com.example.security.url.constants.resultconstant;
import com.example.security.url.utils.jwttokenutil;
import com.example.security.url.utils.responseutils;
import lombok.sneakythrows;
import lombok.extern.slf4j.slf4j;
import org.apache.commons.lang3.stringutils;
import org.springframework.beans.factory.annotation.value;
import org.springframework.security.access.accessdeniedexception;
import org.springframework.security.authentication.usernamepasswordauthenticationtoken;
import org.springframework.security.core.authenticationexception;
import org.springframework.security.core.context.securitycontextholder;
import org.springframework.security.core.userdetails.userdetails;
import org.springframework.security.core.userdetails.userdetailsservice;
import org.springframework.security.core.userdetails.usernamenotfoundexception;
import org.springframework.security.web.authentication.webauthenticationdetailssource;
import org.springframework.util.antpathmatcher;
import org.springframework.util.pathmatcher;
import org.springframework.web.filter.onceperrequestfilter;
import javax.annotation.resource;
import javax.servlet.filterchain;
import javax.servlet.servletexception;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import java.io.ioexception;
/**
* 请求的httpservletrequest流只能读一次,下一次就不能读取了,
* 因此这里要使用自定义的multireadhttpservletrequest工具解决流只能读一次的问题
*
* @author deyou kong
* @description 用户登录鉴权过滤器 filter
* @date 2023/2/10 2:25 下午
*/
@slf4j
public class loginfilter extends onceperrequestfilter {
@resource
private userdetailsservice userdetailsservice;
@resource
private jwttokenutil jwttokenutil;
@resource
ignoreurlsconfig ignoreurlsconfig;
@value("${jwt.tokenheader}")
private string tokenheader;
@value("${jwt.tokentype}")
private string tokentype;
@value("${server.servlet.context-path}")
private string contextpath;
@sneakythrows
@override
protected void dofilterinternal(httpservletrequest request,
httpservletresponse response, filterchain chain) throws servletexception, ioexception {
string requesturi = request.getrequesturi();
log.info("loginfilter -> dofilterinternal,请求url:{}", requesturi);
// 如果requesturi在白名单中直接放行
try {
pathmatcher pathmatcher = new antpathmatcher();
for (string url : ignoreurlsconfig.geturls()) {
string requesturl = contextpath + url;
if (pathmatcher.match((requesturl), requesturi)) {
chain.dofilter(request, response);
return;
}
}
// 验证token
string token = request.getheader(tokenheader);
if (stringutils.isallblank(token)){
throw new loginexception(resultconstant.not_token);
}
if (!token.startswith(tokentype)){
throw new loginexception(resultconstant.token_reg_fail);
}
string authtoken = token.substring(tokentype.length());
if (jwttokenutil.istokenexpired(authtoken)){
throw new loginexception(resultconstant.token_invalid);
}
string username = jwttokenutil.getusernamefromtoken(authtoken);
//if (username != null && securitycontextholder.getcontext().getauthentication() == null) {
if (username != null) {
userdetails userdetails = userdetailsservice.loaduserbyusername(username);
if (userdetails != null) {
// token 中的用户在数据库中查询到数据,开始进行密码验证
usernamepasswordauthenticationtoken authentication = new usernamepasswordauthenticationtoken(userdetails, null, userdetails.getauthorities());
authentication.setdetails(new webauthenticationdetailssource().builddetails(request));
securitycontextholder.getcontext().setauthentication(authentication);
chain.dofilter(request, response);
return;
}
}
} catch (loginexception e) {
commonresult<string> result = commonresult.loginfailed(e.getmessage());
responseutils.out(response, result);
}catch (exception e){
e.printstacktrace();
commonresult<string> result = commonresult.loginfailed(resultconstant.sys_error);
responseutils.out(response, result);
return ;
}
}
}
4、动态权限过滤器,用于实现基于路径的动态权限过滤:securityfilter extends abstractsecurityinterceptor implements filter
package com.example.security.url.filter;
import com.example.security.url.url.urlaccessdecisionmanager;
import com.example.security.url.property.ignoreurlsconfig;
import com.example.security.url.url.urlfilterinvocationsecuritymetadatasource;
import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.annotation.value;
import org.springframework.http.httpmethod;
import org.springframework.security.access.intercept.abstractsecurityinterceptor;
import org.springframework.security.access.intercept.interceptorstatustoken;
import org.springframework.security.web.filterinvocation;
import org.springframework.util.antpathmatcher;
import org.springframework.util.pathmatcher;
import javax.annotation.resource;
import javax.servlet.*;
import javax.servlet.http.httpservletrequest;
import java.io.ioexception;
/**
* 动态权限过滤器,用于实现基于路径的动态权限过滤
*/
@slf4j
public class securityfilter extends abstractsecurityinterceptor implements filter {
@resource
private urlfilterinvocationsecuritymetadatasource urlfilterinvocationsecuritymetadatasource;
@resource
private ignoreurlsconfig ignoreurlsconfig;
@value("${server.servlet.context-path}")
private string contextpath;
@resource
public void setaccessdecisionmanager(urlaccessdecisionmanager urlaccessdecisionmanager) {
super.setaccessdecisionmanager(urlaccessdecisionmanager);
}
@override
public void init(filterconfig filterconfig) {
}
@override
public void dofilter(servletrequest servletrequest, servletresponse servletresponse, filterchain filterchain) throws ioexception, servletexception {
httpservletrequest request = (httpservletrequest) servletrequest;
filterinvocation fi = new filterinvocation(servletrequest, servletresponse, filterchain);
log.info("securityfilter动态权限过滤器,用于实现基于路径的动态权限过滤");
/**
* 仿照onceperrequestfilter,解决filter执行两次的问题
* 执行两次原因:securityconfig中,@bean和addfilter相当于向容器注入了两次
* 解决办法:1是去掉@bean,但filter中若有引用注入容器的其它资源,则会报错
* 2就是request中保存一个attribute来判断该请求是否已执行过
*/
string alreadyfilteredattributename = getalreadyfilteredattributename();
boolean hasalreadyfilteredattribute = request.getattribute(alreadyfilteredattributename) != null;
if (hasalreadyfilteredattribute) {
fi.getchain().dofilter(fi.getrequest(), fi.getresponse());
return;
}
request.setattribute(alreadyfilteredattributename, boolean.true);
//options请求直接放行
if (request.getmethod().equals(httpmethod.options.tostring())) {
fi.getchain().dofilter(fi.getrequest(), fi.getresponse());
return;
}
//白名单请求直接放行
pathmatcher pathmatcher = new antpathmatcher();
for (string path : ignoreurlsconfig.geturls()) {
if (pathmatcher.match(contextpath + path, request.getrequesturi())) {
fi.getchain().dofilter(fi.getrequest(), fi.getresponse());
return;
}
}
//此处会调用accessdecisionmanager中的decide方法进行鉴权操作
interceptorstatustoken token = super.beforeinvocation(fi);
try {
fi.getchain().dofilter(fi.getrequest(), fi.getresponse());
} finally {
super.afterinvocation(token, null);
}
}
@override
public void destroy() {
urlfilterinvocationsecuritymetadatasource.cleardatasource();
}
@override
public class<?> getsecureobjectclass() {
return filterinvocation.class;
}
@override
public urlfilterinvocationsecuritymetadatasource obtainsecuritymetadatasource() {
log.info("securityfilter返回urlfilterinvocationsecuritymetadatasource对象");
return urlfilterinvocationsecuritymetadatasource;
}
protected string getalreadyfilteredattributename() {
return this.getclass().getname() + ".filtered";
}
}
5、未登录访问控制类:adminauthenticationentrypoint implements authenticationentrypoint
package com.example.security.url.filter;
import com.alibaba.fastjson.json;
import com.example.security.url.common.result.commonresult;
import com.example.security.url.utils.responseutils;
import lombok.extern.slf4j.slf4j;
import org.springframework.security.core.authenticationexception;
import org.springframework.security.web.authenticationentrypoint;
import javax.servlet.servletexception;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import java.io.ioexception;
/**
* 在实现 userdetailsservice 接口的类中抛出 org.springframework.security.core.userdetails.usernamenotfoundexception 异常都会被此类捕获
* @author deyou kong
* @description 登录失败处理类/未登录,
* @date 2023/2/10 2:19 下午
*/
@slf4j
public class loginfailhandlerentrypoint implements authenticationentrypoint {
@override
public void commence(httpservletrequest request, httpservletresponse response, authenticationexception authexception) throws ioexception, servletexception {
log.warn("loginfailhandlerentrypoint 登录失败处理类");
responseutils.out(response, commonresult.loginfailed(authexception.getlocalizedmessage()));
}
}responseutils 工具类文末附上
6、获取访问url所需要的角色信息类:urlfilterinvocationsecuritymetadatasource implements filterinvocationsecuritymetadatasource
package com.example.security.url.url;
import com.baomidou.mybatisplus.core.conditions.query.lambdaquerywrapper;
import com.example.security.url.property.ignoreurlsconfig;
import com.example.security.url.constants.resultconstant;
import com.example.security.url.dao.permissionmapper;
import com.example.security.url.dao.rolemapper;
import com.example.security.url.dao.rolepermissionmapper;
import com.example.security.url.entity.permission;
import com.example.security.url.entity.role;
import com.example.security.url.entity.rolepermission;
import lombok.extern.slf4j.slf4j;
import org.springframework.security.access.configattribute;
import org.springframework.security.access.securityconfig;
import org.springframework.security.web.filterinvocation;
import org.springframework.security.web.access.intercept.filterinvocationsecuritymetadatasource;
import org.springframework.util.antpathmatcher;
import org.springframework.util.collectionutils;
import javax.annotation.resource;
import java.util.collection;
import java.util.list;
import java.util.set;
import java.util.stream.collectors;
/**
* @author deyou kong
* @description 访问url需要的角色权限
* @date 2023/2/10 4:19 下午
*/
@slf4j
public class urlfilterinvocationsecuritymetadatasource implements filterinvocationsecuritymetadatasource {
/**
* 正则匹配匹配
*/
antpathmatcher pathmatcher = new antpathmatcher();
@resource
permissionmapper permissionmapper;
@resource
rolepermissionmapper rolepermissionmapper;
@resource
rolemapper rolemapper;
@resource
ignoreurlsconfig ignoreurlsconfig;
private list<configattribute> allconfigattributes;
public void cleardatasource() {
allconfigattributes.clear();
allconfigattributes = null;
}
/***
* 返回该url所需要的用户权限信息
*
* @param object: 储存请求url信息
* @return: null:标识不需要任何权限都可以访问
*/
@override
public collection<configattribute> getattributes(object object) throws illegalargumentexception {
log.info("urlfilterinvocationsecuritymetadatasource获取请求url所需角色");
// 获取当前请求url
string requesturl = ((filterinvocation) object).getrequesturl();
int index = requesturl.indexof("?");
if (index != -1){
requesturl = requesturl.substring(0, index);
}
// 白名单,设置需要的角色为null
for (string url : ignoreurlsconfig.geturls()) {
if (url.equals(requesturl) || pathmatcher.match(url , requesturl)) {
return null;
}
}
// 数据库中所有的菜单
list<permission> permissionlist = permissionmapper.selectlist(null);
if (collectionutils.isempty(permissionlist)){
return null;
}
for (permission permission : permissionlist) {
// 与请求地址进行匹配,获取该url所对应的权限
if (pathmatcher.match(permission.geturl()+"/**", requesturl)){
list<rolepermission> permissions = rolepermissionmapper.selectlist(new lambdaquerywrapper<rolepermission>().eq(rolepermission::getpermissionid, permission.getid()));
if (!collectionutils.isempty(permissions)){
set<integer> roleidset = permissions.stream().map(rolepermission::getroleid).collect(collectors.toset());
list<role> rolelist = rolemapper.selectbatchids(roleidset);
list<string> rolestringlist = rolelist.stream().map(role::getcode).collect(collectors.tolist());
// 保存该url对应角色权限信息
return securityconfig.createlist(rolestringlist.toarray(new string[rolestringlist.size()]));
}
}
}
// 如果数据中没有找到相应url资源则为无权限访问
return securityconfig.createlist(resultconstant.request_forbidden_role);
}
@override
public collection<configattribute> getallconfigattributes() {
return null;
}
@override
public boolean supports(class<?> aclass) {
return filterinvocation.class.isassignablefrom(aclass);
}
}
7、权限认证处理类:urlaccessdecisionmanager implements accessdecisionmanager,认证失败抛出:accessdeniedexception 异常
package com.example.security.url.url;
import com.example.security.url.constants.resultconstant;
import lombok.extern.slf4j.slf4j;
import org.springframework.security.access.accessdecisionmanager;
import org.springframework.security.access.accessdeniedexception;
import org.springframework.security.access.configattribute;
import org.springframework.security.authentication.insufficientauthenticationexception;
import org.springframework.security.core.authentication;
import org.springframework.security.core.grantedauthority;
import org.springframework.stereotype.component;
import java.util.collection;
/**
* @author deyou kong
* @description 权限认证处理类
* @date 2023/2/10 4:37 下午
*/
@slf4j
public class urlaccessdecisionmanager implements accessdecisionmanager {
/**
*
* @param authentication
* @param o
* @param configattributes url所需要的角色权限列表:string[],urlroleneedfilterinvocationsecuritymetadatasource.getattributes返回的对象
* @throws accessdeniedexception
* @throws insufficientauthenticationexception
*/
@override
public void decide(authentication authentication, object o, collection<configattribute> configattributes) throws accessdeniedexception, insufficientauthenticationexception {
log.info("urlaccessdecisionmanager --- > decide");
// 遍历角色
for (configattribute configattribute : configattributes) {
// 当前url请求需要的权限
string needrole = configattribute.getattribute();
if (needrole.equals(resultconstant.request_forbidden_role)){
throw new accessdeniedexception(resultconstant.request_forbidden);
}
// 只要包含其中一个角色即可访问
collection<? extends grantedauthority> authorities = authentication.getauthorities();
for (grantedauthority authority : authorities) {
if (authority.getauthority().equals(needrole)) {
return;
}
}
}
throw new accessdeniedexception(resultconstant.request_forbidden);
}
@override
public boolean supports(configattribute configattribute) {
return true;
}
@override
public boolean supports(class<?> aclass) {
return true;
}
}
8、权限认证失败后的处理类:urlaccessdeniedhandler implements accessdeniedhandler
package com.example.security.url.url;
import com.example.security.url.common.result.commonresult;
import com.example.security.url.constants.resultconstant;
import com.example.security.url.utils.responseutils;
import lombok.extern.slf4j.slf4j;
import org.springframework.security.access.accessdeniedexception;
import org.springframework.security.web.access.accessdeniedhandler;
import javax.servlet.servletexception;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import java.io.ioexception;
/**
* 在实现 accessdecisionmanager 接口中抛出 org.springframework.security.access.accessdeniedexception 异常会被这里捕获
* @author deyou kong
* @description 权限认证失败处理类handler
* @date 2023/2/10 4:53 下午
*/
@slf4j
public class urlaccessdeniedhandler implements accessdeniedhandler {
@override
public void handle(httpservletrequest httpservletrequest, httpservletresponse httpservletresponse, accessdeniedexception e) throws ioexception, servletexception {
log.info("urlaccessdeniedhandler权限认证失败处理类");
responseutils.out(httpservletresponse, commonresult.forbidden(e.getlocalizedmessage()));
}
}
9、核心配置securityconfig
package com.example.security.url.config;
import com.example.security.url.filter.loginfilter;
import com.example.security.url.filter.securityfilter;
import com.example.security.url.filter.loginfailhandlerentrypoint;
import com.example.security.url.url.urlaccessdecisionmanager;
import com.example.security.url.url.urlaccessdeniedhandler;
import com.example.security.url.property.ignoreurlsconfig;
import com.example.security.url.url.urlfilterinvocationsecuritymetadatasource;
import com.example.security.url.utils.md5passwordencoder;
import lombok.extern.slf4j.slf4j;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.http.httpmethod;
import org.springframework.security.config.annotation.objectpostprocessor;
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.builders.websecurity;
import org.springframework.security.config.annotation.web.configuration.enablewebsecurity;
import org.springframework.security.config.annotation.web.configuration.websecurityconfigureradapter;
import org.springframework.security.config.annotation.web.configurers.expressionurlauthorizationconfigurer;
import org.springframework.security.crypto.password.passwordencoder;
import org.springframework.security.web.access.intercept.filtersecurityinterceptor;
import org.springframework.security.web.authentication.usernamepasswordauthenticationfilter;
import javax.annotation.resource;
@configuration
@enablewebsecurity
@enableglobalmethodsecurity(prepostenabled = true)
@slf4j
public class securityconfig extends websecurityconfigureradapter {
@resource
ignoreurlsconfig ignoreurlsconfig;
@override
protected void configure(httpsecurity http) throws exception {
expressionurlauthorizationconfigurer<httpsecurity>.expressionintercepturlregistry registry = http.antmatcher("/**").authorizerequests();
// 禁用csrf 开启跨域
http.csrf().disable().cors();
// 未登录认证异常
http.exceptionhandling().authenticationentrypoint(loginfailhandlerentrypoint());
// 登录过后访问无权限的接口时自定义403响应内容
http.exceptionhandling().accessdeniedhandler(urlaccessdeniedhandler());
// url权限认证处理
registry.withobjectpostprocessor(new objectpostprocessor<filtersecurityinterceptor>() {
@override
public <o extends filtersecurityinterceptor> o postprocess(o o) {
o.setsecuritymetadatasource(urlfilterinvocationsecuritymetadatasource());
o.setaccessdecisionmanager(urlaccessdecisionmanager());
return o;
}
});
// options(选项):查找适用于一个特定网址资源的通讯选择。 在不需执行具体的涉及数据传输的动作情况下, 允许客户端来确定与资源相关的选项以及 / 或者要求, 或是一个服务器的性能
registry.antmatchers(httpmethod.options, "/**").denyall();
// 自动登录 - cookie储存方式
registry.and().rememberme();
// 其余所有请求都需要认证
registry.anyrequest().authenticated();
// 防止iframe 造成跨域
registry.and().headers().frameoptions().disable();
// 自定义过滤器在登录时认证用户名、密码
http.addfilterat(loginfilter(), usernamepasswordauthenticationfilter.class)
.addfilterbefore(securityfilter(), filtersecurityinterceptor.class);
}
/**
* 忽略拦截url或静态资源文件夹 - web.ignoring(): 会直接过滤该url - 将不会经过spring security过滤器链
* http.permitall(): 不会绕开springsecurity验证,相当于是允许该路径通过
* @param web
* @throws exception
*/
@override
public void configure(websecurity web) throws exception {
web.ignoring().antmatchers(httpmethod.get,
"/favicon.ico",
"/*.html",
"/**/*.css",
"/**/*.js");
}
@override
protected void configure(authenticationmanagerbuilder auth) throws exception {
auth.userdetailsservice(userdetailsservice()).passwordencoder(passwordencoder());
}
/**
* 登录过滤器
*/
@bean
public loginfilter loginfilter(){
return new loginfilter();
}
/**
* 登录失败处理类
*/
@bean
public loginfailhandlerentrypoint loginfailhandlerentrypoint(){
return new loginfailhandlerentrypoint();
};
/**
* 获取访问url所需要的角色信息
*/
@bean
public urlfilterinvocationsecuritymetadatasource urlfilterinvocationsecuritymetadatasource(){
return new urlfilterinvocationsecuritymetadatasource();
};
/**
* 认证权限处理 - 将可以请求url的角色权限与当前登录用户的角色做对比,如果包含其中一个角色即可正常访问
*/
@bean
public urlaccessdecisionmanager urlaccessdecisionmanager(){
return new urlaccessdecisionmanager();
};
/**
* 自定义访问无权限接口时403响应内容
*/
@bean
public urlaccessdeniedhandler urlaccessdeniedhandler(){
return new urlaccessdeniedhandler();
};
@bean
public securityfilter securityfilter() {
return new securityfilter();
}
/**
* 密码加密类
* @return
*/
@bean
public passwordencoder passwordencoder() {
return new md5passwordencoder();
}
}
其他工具类
1、自定义异常
package com.example.security.url.exception;
import lombok.data;
/**
* @author deyou kong
* @description 登录异常
* @date 2023/2/13 9:18 上午
*/
@data
public class loginexception extends runtimeexception{
private string message;
public loginexception(string message){
this.message = message;
}
}
2、读取配置文件配置
package com.example.security.url.property;
import lombok.data;
import org.springframework.boot.context.properties.configurationproperties;
import org.springframework.stereotype.component;
import java.util.list;
@data
@component
@configurationproperties(prefix = "secure.ignored")
public class ignoreurlsconfig {
private list<string> urls;
}
3、md5加密工具类
package com.example.security.url.utils;
import java.math.biginteger;
import java.security.messagedigest;
import java.security.nosuchalgorithmexception;
/**
* @author deyou kong
* @description md5算法
* @date 2023/2/10 7:16 下午
*/
public class md5utils {
/**
* 使用md5的算法进行加密
*/
public static string encode(string plaintext) {
byte[] secretbytes = null;
try {
secretbytes = messagedigest.getinstance("md5").digest(
plaintext.getbytes());
} catch (nosuchalgorithmexception e) {
throw new runtimeexception("没有md5这个算法!");
}
string md5code = new biginteger(1, secretbytes).tostring(16);// 16进制数字
// 如果生成数字未满32位,需要前面补0
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
return md5code;
}
}
4、md5passwordencoder
package com.example.security.url.utils;
import lombok.extern.slf4j.slf4j;
import org.springframework.security.crypto.password.passwordencoder;
/**
* @author deyou kong
* @description
* @date 2023/2/10 7:16 下午
*/
@slf4j
public class md5passwordencoder implements passwordencoder {
@override
public boolean matches(charsequence rawpassword, string encodedpassword) {
log.info("md5passwordencoder的matches");
return encodedpassword.equals(md5utils.encode((string)rawpassword));
}
@override
public string encode(charsequence rawpassword) {
log.info("md5passwordencoder的encode");
return md5utils.encode((string)rawpassword);
}
}
5、token工具类
package com.example.security.url.utils;
import cn.hutool.core.date.dateutil;
import cn.hutool.core.util.strutil;
import com.example.security.url.constants.resultconstant;
import io.jsonwebtoken.claims;
import io.jsonwebtoken.expiredjwtexception;
import io.jsonwebtoken.jwts;
import io.jsonwebtoken.io.decoders;
import io.jsonwebtoken.security.keys;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import org.springframework.beans.factory.annotation.value;
import org.springframework.security.core.userdetails.userdetails;
import org.springframework.stereotype.component;
import javax.security.sasl.authenticationexception;
import java.util.date;
import java.util.hashmap;
import java.util.map;
/**
* jwttoken生成的工具类
* jwt token的格式:header.payload.signature
* header的格式(算法、token的类型):
* {"alg": "hs512","typ": "jwt"}
* payload的格式(用户名、创建时间、生成时间):
* {"sub":"wang","created":1489079981393,"exp":1489684781}
* signature的生成算法:
* hmacsha512(base64urlencode(header) + "." +base64urlencode(payload),secret)
*/
@component
public class jwttokenutil {
private static final logger logger = loggerfactory.getlogger(jwttokenutil.class);
private static final string claim_key_username = "sub";
private static final string claim_key_created = "created";
@value("${jwt.secret}")
private string secret;
@value("${jwt.expiration}")
private long expiration;
@value("${jwt.tokentype}")
private string tokentype;
/**
* 根据负责生成jwt的token
*/
private string generatetoken(map<string, object> claims) {
return jwts.builder()
.setclaims(claims)
.setexpiration(generateexpirationdate())
.signwith(keys.hmacshakeyfor(decoders.base64.decode(secret)))
.compact();
}
/**
* 从token中获取jwt中的负载
*/
private claims getclaimsfromtoken(string token) throws authenticationexception {
claims claims = null;
try {
claims = jwts.parserbuilder()
.setsigningkey(keys.hmacshakeyfor(decoders.base64.decode(secret)))
.build()
.parseclaimsjws(token)
.getbody();
} catch (expiredjwtexception e){
claims =e.getclaims();
} catch (exception e) {
logger.error("获取token:【{}】中的jwt负载失败:【{}】", token, e.getmessage());
}
return claims;
}
/**
* 生成token的过期时间
*/
private date generateexpirationdate() {
return new date(system.currenttimemillis() + expiration * 1000);
}
/**
* 从token中获取登录用户名
*/
public string getusernamefromtoken(string token) {
string username;
try {
claims claims = getclaimsfromtoken(token);
username = claims.getsubject();
} catch (exception e) {
username = null;
}
return username;
}
/**
* 验证token中的用户是否还有效
*
* @param token 客户端传入的token
* @param userdetails 从数据库中查询出来的用户信息
*/
public boolean validatetoken(string token, userdetails userdetails) throws authenticationexception {
string username = getusernamefromtoken(token);
return username.equals(userdetails.getusername()) && !istokenexpired(token);
}
/**
* 判断token是否已经失效
*/
public boolean istokenexpired(string token) throws authenticationexception {
date expireddate = getexpireddatefromtoken(token);
return expireddate.before(new date());
}
/**
* 从token中获取过期时间
*/
private date getexpireddatefromtoken(string token) throws authenticationexception {
claims claims = getclaimsfromtoken(token);
return claims.getexpiration();
}
/**
* 根据用户信息生成token
*/
public string generatetoken(userdetails userdetails) {
map<string, object> claims = new hashmap<>();
claims.put(claim_key_username, userdetails.getusername());
claims.put(claim_key_created, new date());
return generatetoken(claims);
}
/**
* 当原来的token没过期时是可以刷新的
*
* @param oldtoken 带tokenhead的token
*/
public string refreshheadtoken(string oldtoken) throws authenticationexception {
if (strutil.isempty(oldtoken)) {
return null;
}
string token = oldtoken.substring(tokentype.length());
if (strutil.isempty(token)) {
return null;
}
//token校验不通过
claims claims = getclaimsfromtoken(token);
if (claims == null) {
return null;
}
//如果token已经过期,不支持刷新
if (istokenexpired(token)) {
return null;
}
//如果token在30分钟之内刚刷新过,返回原token
if (tokenrefreshjustbefore(token, 30 * 60)) {
return token;
} else {
claims.put(claim_key_created, new date());
return generatetoken(claims);
}
}
/**
* 判断token在指定时间内是否刚刚刷新过
*
* @param token 原token
* @param time 指定时间(秒)
*/
private boolean tokenrefreshjustbefore(string token, int time) throws authenticationexception {
claims claims = getclaimsfromtoken(token);
date created = claims.get(claim_key_created, date.class);
date refreshdate = new date();
//刷新时间在创建时间的指定时间内
if (refreshdate.after(created) && refreshdate.before(dateutil.offsetsecond(created, time))) {
return true;
}
return false;
}
}6、输入流工具类
package com.example.security.url.utils;
import com.alibaba.fastjson.jsonobject;
import com.alibaba.fastjson.serializer.serializerfeature;
import com.example.security.url.common.result.commonresult;
import javax.servlet.http.httpservletresponse;
import java.io.ioexception;
/**
* @author deyou kong
* @description 响应处理类
* @date 2023/2/9 3:33 下午
*/
public class responseutils {
public static void out(httpservletresponse response, commonresult result) throws ioexception {
response.setcharacterencoding("utf-8");
response.setcontenttype("application/json");
response.getwriter().println(jsonobject.tojsonstring(result, serializerfeature.writemapnullvalue)); // 保留值为null的字段
response.getwriter().flush();
}
}
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论