当前位置: 代码网 > it编程>编程语言>Java > Spring Security实现动态路由权限控制方式

Spring Security实现动态路由权限控制方式

2024年08月14日 Java 我要评论
spring security实现动态路由权限控制主要步骤如下:1、securityuser implements userdetails 接口中的方法2、自定义认证:userdetailsservi

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();
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com