欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Security實(shí)現(xiàn)動(dòng)態(tài)路由權(quán)限控制方式

 更新時(shí)間:2024年08月14日 16:29:44   作者:DeyouKong  
這篇文章主要介紹了Spring Security實(shí)現(xiàn)動(dòng)態(tài)路由權(quán)限控制方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

Spring Security實(shí)現(xiàn)動(dòng)態(tài)路由權(quán)限控制

主要步驟如下:

  • 1、SecurityUser implements UserDetails 接口中的方法
  • 2、自定義認(rèn)證:UserDetailsServiceImpl implements UserDetailsService
  • 3、添加登錄過(guò)濾器LoginFilter extends OncePerRequestFilter

每次訪問(wèn)接口都會(huì)經(jīng)過(guò)此,我們可以在這里記錄請(qǐng)求參數(shù)、響應(yīng)內(nèi)容,或者處理前后端分離情況下, 以token換用戶權(quán)限信息,token是否過(guò)期,請(qǐng)求頭類型是否正確,防止非法請(qǐng)求等等

  • 4、動(dòng)態(tài)權(quán)限過(guò)濾器,用于實(shí)現(xiàn)基于路徑的動(dòng)態(tài)權(quán)限過(guò)濾:SecurityFilter extends AbstractSecurityInterceptor implements Filter
  • 5、未登錄訪問(wèn)控制類:AdminAuthenticationEntryPoint implements AuthenticationEntryPoint
  • 6、獲取訪問(wèn)URL所需要的角色信息類:UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource
  • 7、權(quán)限認(rèn)證處理類:UrlAccessDecisionManager implements AccessDecisionManager,認(rèn)證失敗拋出:AccessDeniedException 異常
  • 8、權(quán)限認(rèn)證失敗后的處理類:UrlAccessDeniedHandler implements AccessDeniedHandler
  • 9、核心配置SecurityConfig

代碼實(shí)現(xiàn)

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驗(yàn)證用戶
 * @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;
        }
    }

    /**
     * 獲取當(dāng)前用戶所具有的角色
     * @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、自定義認(rèn)證: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實(shí)現(xiàn)類");
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername, username);
        User user = userMapper.selectOne(queryWrapper);
        //如果用戶被禁用,則不再查詢權(quán)限表
        if (user == null){
            // 拋出異常,會(huì)被LoginFailHandlerEntryPoint捕獲
            throw new UsernameNotFoundException(ResultConstant.USER_NOT_EXIST);
            //return null;
        }
        return new SecurityUser(user, getUserRoles(user.getId()));
    }

    /**
     * 根據(jù)用戶id獲取角色權(quán)限信息
     *
     * @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);
        // 判斷用戶有沒(méi)有角色,沒(méi)有角色,直接返回空列表
        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、添加登錄過(guò)濾器LoginFilter extends OncePerRequestFilter

每次訪問(wèn)接口都會(huì)經(jīng)過(guò)此,我們可以在這里記錄請(qǐng)求參數(shù)、響應(yīng)內(nèi)容等日志,或者處理前后端分離情況下,以token換用戶權(quán)限信息,token是否過(guò)期,請(qǐng)求頭類型是否正確,防止非法請(qǐng)求等等

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;

/**
 * 請(qǐng)求的HttpServletRequest流只能讀一次,下一次就不能讀取了,
 * 因此這里要使用自定義的MultiReadHttpServletRequest工具解決流只能讀一次的問(wèn)題
 *
 * @author Deyou Kong
 * @description 用戶登錄鑒權(quán)過(guò)濾器 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,請(qǐng)求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;
                }
            }

            // 驗(yàn)證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 中的用戶在數(shù)據(jù)庫(kù)中查詢到數(shù)據(jù),開(kāi)始進(jìn)行密碼驗(yàn)證
                    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、動(dòng)態(tài)權(quán)限過(guò)濾器,用于實(shí)現(xiàn)基于路徑的動(dòng)態(tài)權(quán)限過(guò)濾: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;

/**
 * 動(dòng)態(tài)權(quán)限過(guò)濾器,用于實(shí)現(xiàn)基于路徑的動(dòng)態(tài)權(quán)限過(guò)濾
 */

@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動(dòng)態(tài)權(quán)限過(guò)濾器,用于實(shí)現(xiàn)基于路徑的動(dòng)態(tài)權(quán)限過(guò)濾");

        /**
         * 仿照OncePerRequestFilter,解決Filter執(zhí)行兩次的問(wèn)題
         * 執(zhí)行兩次原因:SecurityConfig中,@Bean和addFilter相當(dāng)于向容器注入了兩次
         * 解決辦法:1是去掉@Bean,但Filter中若有引用注入容器的其它資源,則會(huì)報(bào)錯(cuò)
         *         2就是request中保存一個(gè)Attribute來(lái)判斷該請(qǐng)求是否已執(zhí)行過(guò)
         */
        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請(qǐng)求直接放行
        if (request.getMethod().equals(HttpMethod.OPTIONS.toString())) {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            return;
        }
        //白名單請(qǐng)求直接放行
        PathMatcher pathMatcher = new AntPathMatcher();
        for (String path : ignoreUrlsConfig.getUrls()) {
            if (pathMatcher.match(contextPath + path, request.getRequestURI())) {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
                return;
            }
        }

        //此處會(huì)調(diào)用AccessDecisionManager中的decide方法進(jìn)行鑒權(quán)操作
        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對(duì)象");
        return urlFilterInvocationSecurityMetadataSource;
    }

    protected String getAlreadyFilteredAttributeName() {
        return this.getClass().getName() + ".FILTERED";
    }
}

5、未登錄訪問(wèn)控制類: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;

/**
 * 在實(shí)現(xiàn) UserDetailsService 接口的類中拋出 org.springframework.security.core.userdetails.UsernameNotFoundException 異常都會(huì)被此類捕獲
 * @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、獲取訪問(wèn)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 訪問(wèn)URL需要的角色權(quán)限
 * @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所需要的用戶權(quán)限信息
     *
     * @param object: 儲(chǔ)存請(qǐng)求url信息
     * @return: null:標(biāo)識(shí)不需要任何權(quán)限都可以訪問(wèn)
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        log.info("UrlFilterInvocationSecurityMetadataSource獲取請(qǐng)求URL所需角色");
        // 獲取當(dāng)前請(qǐng)求url
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        int index = requestUrl.indexOf("?");
        if (index != -1){
            requestUrl = requestUrl.substring(0, index);
        }
        // 白名單,設(shè)置需要的角色為null
        for (String url : ignoreUrlsConfig.getUrls()) {
            if (url.equals(requestUrl) || pathMatcher.match(url , requestUrl)) {
                return null;
            }
        }
        // 數(shù)據(jù)庫(kù)中所有的菜單
        List<Permission> permissionList = permissionMapper.selectList(null);
        if (CollectionUtils.isEmpty(permissionList)){
            return null;
        }
        for (Permission permission : permissionList) {
            // 與請(qǐng)求地址進(jìn)行匹配,獲取該url所對(duì)應(yīng)的權(quán)限
            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對(duì)應(yīng)角色權(quán)限信息
                    return SecurityConfig.createList(roleStringList.toArray(new String[roleStringList.size()]));
                }
            }
        }
        // 如果數(shù)據(jù)中沒(méi)有找到相應(yīng)url資源則為無(wú)權(quán)限訪問(wèn)
        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、權(quán)限認(rèn)證處理類:UrlAccessDecisionManager implements AccessDecisionManager,認(rèn)證失敗拋出: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 權(quán)限認(rèn)證處理類
 * @date 2023/2/10 4:37 下午
 */

@Slf4j
public class UrlAccessDecisionManager implements AccessDecisionManager {
    /**
     *
     * @param authentication
     * @param o
     * @param configAttributes  URL所需要的角色權(quán)限列表:String[],UrlRoleNeedFilterInvocationSecurityMetadataSource.getAttributes返回的對(duì)象
     * @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) {
            // 當(dāng)前url請(qǐng)求需要的權(quán)限
            String needRole = configAttribute.getAttribute();
            if (needRole.equals(ResultConstant.REQUEST_FORBIDDEN_ROLE)){
                throw new AccessDeniedException(ResultConstant.REQUEST_FORBIDDEN);
            }
            // 只要包含其中一個(gè)角色即可訪問(wèn)
            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、權(quán)限認(rèn)證失敗后的處理類: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;

/**
 * 在實(shí)現(xiàn) AccessDecisionManager 接口中拋出 org.springframework.security.access.AccessDeniedException 異常會(huì)被這里捕獲
 * @author Deyou Kong
 * @description 權(quán)限認(rèn)證失敗處理類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權(quán)限認(rèn)證失敗處理類");
        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 開(kāi)啟跨域
        http.csrf().disable().cors();

        // 未登錄認(rèn)證異常
        http.exceptionHandling().authenticationEntryPoint(loginFailHandlerEntryPoint());

        // 登錄過(guò)后訪問(wèn)無(wú)權(quán)限的接口時(shí)自定義403響應(yīng)內(nèi)容
        http.exceptionHandling().accessDeniedHandler(urlAccessDeniedHandler());

        // url權(quán)限認(rèn)證處理
        registry.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
            @Override
            public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource());
                o.setAccessDecisionManager(urlAccessDecisionManager());
                return o;
            }
        });

        // OPTIONS(選項(xiàng)):查找適用于一個(gè)特定網(wǎng)址資源的通訊選擇。 在不需執(zhí)行具體的涉及數(shù)據(jù)傳輸?shù)膭?dòng)作情況下, 允許客戶端來(lái)確定與資源相關(guān)的選項(xiàng)以及 / 或者要求, 或是一個(gè)服務(wù)器的性能
        registry.antMatchers(HttpMethod.OPTIONS, "/**").denyAll();
        // 自動(dòng)登錄 - cookie儲(chǔ)存方式
        registry.and().rememberMe();
        // 其余所有請(qǐng)求都需要認(rèn)證
        registry.anyRequest().authenticated();
        // 防止iframe 造成跨域
        registry.and().headers().frameOptions().disable();

        // 自定義過(guò)濾器在登錄時(shí)認(rèn)證用戶名、密碼
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(securityFilter(), FilterSecurityInterceptor.class);

    }

    /**
     * 忽略攔截url或靜態(tài)資源文件夾 - web.ignoring(): 會(huì)直接過(guò)濾該url - 將不會(huì)經(jīng)過(guò)Spring Security過(guò)濾器鏈
     *                             http.permitAll(): 不會(huì)繞開(kāi)springsecurity驗(yàn)證,相當(dāng)于是允許該路徑通過(guò)
     * @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());
    }

    /**
     * 登錄過(guò)濾器
     */
    @Bean
    public LoginFilter loginFilter(){
        return new LoginFilter();
    }

    /**
     * 登錄失敗處理類
     */
    @Bean
    public LoginFailHandlerEntryPoint loginFailHandlerEntryPoint(){
        return new LoginFailHandlerEntryPoint();
    };

    /**
     * 獲取訪問(wèn)url所需要的角色信息
     */
    @Bean
    public UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource(){
        return new UrlFilterInvocationSecurityMetadataSource();
    };
    /**
     * 認(rèn)證權(quán)限處理 - 將可以請(qǐng)求URL的角色權(quán)限與當(dāng)前登錄用戶的角色做對(duì)比,如果包含其中一個(gè)角色即可正常訪問(wèn)
     */
    @Bean
    public UrlAccessDecisionManager urlAccessDecisionManager(){
        return new UrlAccessDecisionManager();
    };
    /**
     * 自定義訪問(wèn)無(wú)權(quán)限接口時(shí)403響應(yīng)內(nèi)容
     */
    @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的算法進(jìn)行加密
     */
    public static String encode(String plainText) {
        byte[] secretBytes = null;
        try {
            secretBytes = MessageDigest.getInstance("md5").digest(
                    plainText.getBytes());
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("沒(méi)有md5這個(gè)算法!");
        }
        String md5code = new BigInteger(1, secretBytes).toString(16);// 16進(jìn)制數(shù)字
        // 如果生成數(shù)字未滿32位,需要前面補(bǔ)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的格式(用戶名、創(chuàng)建時(shí)間、生成時(shí)間):
 * {"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;


    /**
     * 根據(jù)負(fù)責(zé)生成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中的負(fù)載
     */
    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負(fù)載失敗:【{}】", token, e.getMessage());
        }
        return claims;
    }

    /**
     * 生成token的過(guò)期時(shí)間
     */
    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;
    }

    /**
     * 驗(yàn)證token中的用戶是否還有效
     *
     * @param token       客戶端傳入的token
     * @param userDetails 從數(shù)據(jù)庫(kù)中查詢出來(lái)的用戶信息
     */
    public boolean validateToken(String token, UserDetails userDetails) throws AuthenticationException {
        String username = getUserNameFromToken(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    /**
     * 判斷token是否已經(jīng)失效
     */
    public boolean isTokenExpired(String token) throws AuthenticationException {
        Date expiredDate = getExpiredDateFromToken(token);
        return expiredDate.before(new Date());
    }

    /**
     * 從token中獲取過(guò)期時(shí)間
     */
    private Date getExpiredDateFromToken(String token) throws AuthenticationException {
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }

    /**
     * 根據(jù)用戶信息生成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);
    }

    /**
     * 當(dāng)原來(lái)的token沒(méi)過(guò)期時(shí)是可以刷新的
     *
     * @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校驗(yàn)不通過(guò)
        Claims claims = getClaimsFromToken(token);
        if (claims == null) {
            return null;
        }
        //如果token已經(jīng)過(guò)期,不支持刷新
        if (isTokenExpired(token)) {
            return null;
        }
        //如果token在30分鐘之內(nèi)剛刷新過(guò),返回原token
        if (tokenRefreshJustBefore(token, 30 * 60)) {
            return token;
        } else {
            claims.put(CLAIM_KEY_CREATED, new Date());
            return generateToken(claims);
        }
    }

    /**
     * 判斷token在指定時(shí)間內(nèi)是否剛剛刷新過(guò)
     *
     * @param token 原token
     * @param time  指定時(shí)間(秒)
     */
    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();
        //刷新時(shí)間在創(chuàng)建時(shí)間的指定時(shí)間內(nèi)
        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 響應(yīng)處理類
 * @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();
    }
}

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java位集合之BitMap實(shí)現(xiàn)和應(yīng)用詳解

    Java位集合之BitMap實(shí)現(xiàn)和應(yīng)用詳解

    這篇文章主要介紹了Java位集合之BitMap實(shí)現(xiàn)和應(yīng)用的相關(guān)資料,BitMap是一種高效的數(shù)據(jù)結(jié)構(gòu),適用于快速排序、去重和查找等操作,通過(guò)簡(jiǎn)單的數(shù)組和位運(yùn)算,可以在Java中實(shí)現(xiàn)BitMap,從而節(jié)省存儲(chǔ)空間并提高性能,需要的朋友可以參考下
    2024-12-12
  • java實(shí)現(xiàn)一個(gè)掃描包的工具類實(shí)例代碼

    java實(shí)現(xiàn)一個(gè)掃描包的工具類實(shí)例代碼

    很多框架,比如springmvc,mybatis等使用注解,為了處理注解,必然要對(duì)包進(jìn)行掃描,所以下面這篇文章主要給大家分享介紹了關(guān)于利用java如何實(shí)現(xiàn)一個(gè)掃描包的工具類,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。
    2017-10-10
  • Java spring事務(wù)及事務(wù)不生效的原因詳解

    Java spring事務(wù)及事務(wù)不生效的原因詳解

    在日常編碼過(guò)程中常常涉及到事務(wù),在前兩天看到一篇文章提到了Spring事務(wù),那么在此總結(jié)下在Spring環(huán)境下事務(wù)失效的幾種原因
    2021-09-09
  • java多線程中的異常處理機(jī)制簡(jiǎn)析

    java多線程中的異常處理機(jī)制簡(jiǎn)析

    在java多線程程序中,所有線程都不允許拋出未捕獲的checked exception,也就是說(shuō)各個(gè)線程需要自己把自己的checked exception處理掉,需要了解的朋友可以參考下
    2012-11-11
  • 教你輕松制作java音樂(lè)播放器

    教你輕松制作java音樂(lè)播放器

    這篇文章主要介紹了如何編寫(xiě)屬于自己的java音樂(lè)播放器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Java報(bào)錯(cuò):java.util.concurrent.ExecutionException的解決辦法

    Java報(bào)錯(cuò):java.util.concurrent.ExecutionException的解決辦法

    在Java并發(fā)編程中,我們經(jīng)常使用java.util.concurrent包提供的工具來(lái)管理和協(xié)調(diào)多個(gè)線程的執(zhí)行,va并發(fā)編程中,然而,在使用這些工具時(shí),可能會(huì)遇到各種各樣的異常,其中之一就是java.util.concurrent.ExecutionException,本文將詳細(xì)分析這種異常的背景、可能的原因
    2024-09-09
  • SpringMVC異常處理的三種方式

    SpringMVC異常處理的三種方式

    在SpringMVC中異常處理是一個(gè)重要的方面,它幫助我們有效地處理應(yīng)用程序中的異常情況,提高用戶體驗(yàn)和系統(tǒng)的穩(wěn)定性,這篇文章主要給大家介紹了關(guān)于SpringMVC異常處理的三種方式,需要的朋友可以參考下
    2024-02-02
  • SpringMVC文件上傳功能實(shí)例解析

    SpringMVC文件上傳功能實(shí)例解析

    這篇文章主要介紹了SpringMVC文件上傳功能實(shí)例解析,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2017-03-03
  • springboot jpa 延遲加載問(wèn)題的2種解決

    springboot jpa 延遲加載問(wèn)題的2種解決

    這篇文章主要介紹了springboot jpa 延遲加載問(wèn)題的2種解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java實(shí)現(xiàn)作業(yè)調(diào)度的示例代碼

    Java實(shí)現(xiàn)作業(yè)調(diào)度的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)SJF算法調(diào)度,要求測(cè)試數(shù)據(jù)可以隨即輸入或從文件中讀入,文中的示例代碼講解詳細(xì),需要的可以參考一下
    2023-04-04

最新評(píng)論