SpringSecurity使用

281 阅读3分钟

SpringSecurity

使用过程中总结出来的比较全且规范的写法

image.png

CustomizeAccessDecisionManager

package com.bieyang.myblog.handler;

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;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class CustomizeAccessDecisionManager implements AccessDecisionManager {

    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        // 获取用户权限列表
        List<String> permissionList = authentication.getAuthorities()
                .stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());
        for (ConfigAttribute item : collection) {
            if (permissionList.contains(item.getAttribute())) {
                return;
            }
        }
        throw new AccessDeniedException("没有操作权限");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }


}

CustomizeAccessDeniedHandler

package com.bieyang.myblog.handler;


import com.alibaba.fastjson.JSON;
import com.bieyang.myblog.enums.ResultCode;
import com.bieyang.myblog.model.vo.ResultVO;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *
 *用来解决认证过的用户访问无权限资源时的异常
 */
@Component
public class CustomizeAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        httpServletResponse.setContentType("application/json; charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new ResultVO<>(ResultCode.NO_PERMISSION,null)));
    }
}

CustomizeAuthenticationEntryPoint

package com.bieyang.myblog.handler;


import com.alibaba.fastjson.JSON;
import com.bieyang.myblog.enums.ResultCode;
import com.bieyang.myblog.model.vo.ResultVO;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 用户未登录处理
 * AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
 */
@Component
public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
        httpServletResponse.setContentType("application/json; charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new ResultVO<>(ResultCode.USER_NOT_LOGIN,null)));
    }
}

CustomizeAuthenticationFailureHandler

package com.bieyang.myblog.handler;


import com.alibaba.fastjson.JSON;
import com.bieyang.myblog.enums.ResultCode;
import com.bieyang.myblog.model.vo.ResultVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

        httpServletResponse.setContentType("application/json; charset=UTF-8");
        log.info("【登录失败】" + e.getMessage());

        if (e instanceof AccountExpiredException) {
            //账号过期
            httpServletResponse.getWriter().write(JSON.toJSONString(new ResultVO<>(ResultCode.USER_ACCOUNT_EXPIRED,e.getMessage())));
        } else if (e instanceof BadCredentialsException) {
            //密码错误
            httpServletResponse.getWriter().write(JSON.toJSONString(new ResultVO<>(ResultCode.USER_CREDENTIALS_ERROR,e.getMessage())));
        } else if (e instanceof CredentialsExpiredException) {
            //密码过期
            httpServletResponse.getWriter().write(JSON.toJSONString(new ResultVO<>(ResultCode.USER_CREDENTIALS_EXPIRED,e.getMessage())));
        } else if (e instanceof DisabledException) {
            //账号不可用
            httpServletResponse.getWriter().write(JSON.toJSONString(new ResultVO<>(ResultCode.USER_ACCOUNT_DISABLE,e.getMessage())));
        } else if (e instanceof LockedException) {
            //账号锁定
            httpServletResponse.getWriter().write(JSON.toJSONString(new ResultVO<>(ResultCode.USER_ACCOUNT_LOCKED,e.getMessage())));
        } else if (e instanceof InternalAuthenticationServiceException) {
            //用户不存在
            httpServletResponse.getWriter().write(JSON.toJSONString(new ResultVO<>(ResultCode.USER_ACCOUNT_NOT_EXIST,e.getMessage())));
        }else{
            //其他错误
            httpServletResponse.getWriter().write(JSON.toJSONString(new ResultVO<>(ResultCode.COMMON_FAIL,e.getMessage())));
        }



    }
}

CustomizeAuthenticationSuccessHandler

package com.bieyang.myblog.handler;


import com.alibaba.fastjson.JSON;
import com.bieyang.myblog.entity.SysUser;
import com.bieyang.myblog.enums.ResultCode;
import com.bieyang.myblog.mapper.SysUserMapper;
import com.bieyang.myblog.model.dto.UserInfoDTO;
import com.bieyang.myblog.model.vo.ResultVO;
import com.bieyang.myblog.utils.BeanCopyUtil;
import com.bieyang.myblog.utils.UserUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private SysUserMapper sysUserMapper;

    /**
     * 登录成功返回结果
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {


        //此处还可以进行一些处理,比如登录成功之后可能需要返回给前台当前用户有哪些菜单权限,
        //进而前台动态的控制菜单的显示等,具体根据自己的业务需求进行扩展
        // 更新用户ip,最近登录时间

        // 组装JWT
        //  UserDTO userModel = (UserDTO) authentication.getPrincipal();



        UserInfoDTO userLoginDTO = BeanCopyUtil.copyObject(UserUtil.getLoginUser(), UserInfoDTO.class);

        //返回数据
        httpServletResponse.setContentType("application/json; charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new ResultVO<>(ResultCode.SUCCESS, userLoginDTO)));

        //更新用户表上次登录时间、更新人、更新时间等字段
        updateUserInfo();
    }


    @Async
    public void updateUserInfo(){
        SysUser sysUser = SysUser.builder()
                .id(UserUtil.getLoginUser().getId())
                .ipAddr(UserUtil.getLoginUser().getIpAddr())
                .ipSource(UserUtil.getLoginUser().getIpSource())
                .recentLogin(UserUtil.getLoginUser().getRecentLogin())
                .build();
        sysUserMapper.updateById(sysUser);

    }
}

CustomizeFilterInvocationSecurityMetadataSource

package com.bieyang.myblog.handler;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.bieyang.myblog.mapper.RoleMapper;
import com.bieyang.myblog.model.dto.ResourceRoleDTO;
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.Collection;
import java.util.List;

//安全元数据源FilterInvocationSecurityMetadataSource
@Component
public class CustomizeFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    /**
     * 资源角色列表
     */
    private static List<ResourceRoleDTO> resourceRoleList;

    @Autowired
    private RoleMapper roleMapper;

    /**
     * 加载资源角色信息
     */
    //@PostConstruct
    private void loadDataSource() {
        resourceRoleList = roleMapper.listResourceRoles();
    }

    /**
     * 清空接口角色信息
     */
    public void clearDataSource() {
        resourceRoleList = null;
    }


    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        // 修改接口角色关系后重新加载
        if (CollectionUtils.isEmpty(resourceRoleList)) {
            this.loadDataSource();
        }
        FilterInvocation fi = (FilterInvocation) object;
        // 获取用户请求方式
        String method = fi.getRequest().getMethod();
        // 获取用户请求Url
        String url = fi.getRequest().getRequestURI();
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        // 获取接口角色信息,若为匿名接口则放行,若无对应角色则禁止
        for (ResourceRoleDTO resourceRoleDTO : resourceRoleList) {
            if (antPathMatcher.match(resourceRoleDTO.getUrl(), url) && resourceRoleDTO.getRequestMethod().equals(method)) {
                List<String> roleList = resourceRoleDTO.getRoleList();
                if (CollectionUtils.isEmpty(roleList)) {
                    return SecurityConfig.createList("disable");
                }
                return SecurityConfig.createList(roleList.toArray(new String[]{}));
            }
        }
        return null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return FilterInvocation.class.isAssignableFrom(aClass);
    }
}

CustomizeLogoutSuccessHandler

package com.bieyang.myblog.handler;


import com.alibaba.fastjson.JSON;
import com.bieyang.myblog.enums.ResultCode;
import com.bieyang.myblog.model.vo.ResultVO;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json; charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new ResultVO<>(ResultCode.SUCCESS,"注销成功!")));
    }
}

CustomizeSessionInformationExpiredStrategy

package com.bieyang.myblog.handler;

import com.alibaba.fastjson.JSON;
import com.bieyang.myblog.enums.ResultCode;
import com.bieyang.myblog.model.vo.ResultVO;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomizeSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent sessionInformationExpiredEvent) throws IOException {
        HttpServletResponse httpServletResponse = sessionInformationExpiredEvent.getResponse();
        httpServletResponse.setContentType("text/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new ResultVO<>(ResultCode.USER_ACCOUNT_USE_BY_OTHERS,null)));
    }
}

WebSecurityConfig

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
     *
     * AccessDeineHandler 用来解决认证过的用户访问无权限资源时的异常
     */
    //登录成功处理逻辑
    @Autowired
    private CustomizeAuthenticationSuccessHandler authenticationSuccessHandler;

    //登录失败处理逻辑
    @Autowired
    private CustomizeAuthenticationFailureHandler authenticationFailureHandler;

    //权限拒绝处理逻辑
    @Autowired
    private CustomizeAccessDeniedHandler accessDeniedHandler;

    //匿名用户访问无权限资源时的异常
    @Autowired
    private CustomizeAuthenticationEntryPoint authenticationEntryPoint;

    //会话失效(账号被挤下线)处理逻辑
    //@Autowired
    //private CustomizeSessionInformationExpiredStrategy sessionInformationExpiredStrategy;

    //登出成功处理逻辑
    @Autowired
    private CustomizeLogoutSuccessHandler logoutSuccessHandler;

    //访问决策管理器
    @Autowired
    private CustomizeAccessDecisionManager accessDecisionManager;

    //实现权限拦截
    @Autowired
    private CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource;




    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        // 设置默认的加密方式(强hash方式加密)
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        //获取用户账号密码及权限信息
        return new UserDetailsServiceImpl();
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //配置认证方式
        auth.userDetailsService(userDetailsService());
    }



//前一个版本
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        http.cors().and().csrf().disable();
//        http.authorizeRequests().
//                antMatchers(  "/swagger-ui/**").permitAll().
//        //antMatchers("/admin/user/menus").hasAuthority("query_user").
//                //antMatchers("/**").fullyAuthenticated().
//                        withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
//                    @Override
//                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
//                        o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
//                        o.setAccessDecisionManager(accessDecisionManager);//决策管理器
//                        return o;
//                    }
//                }).
//                //登出
//                and().
//                logout().
//                permitAll().//允许所有用户
//                logoutSuccessHandler(logoutSuccessHandler).//登出成功处理逻辑
//                deleteCookies("JSESSIONID").//登出之后删除cookie
//                //登入
//                        and().formLogin().
//                // loginProcessingUrl("/login").//处理逻辑地址
//                permitAll().//允许所有用户
//                successHandler(authenticationSuccessHandler).//登录成功处理逻辑
//                failureHandler(authenticationFailureHandler).//登录失败处理逻辑
//                //异常处理(权限拒绝、登录失效等)
//                        and().exceptionHandling().
//                accessDeniedHandler(accessDeniedHandler).//权限拒绝处理逻辑
//                authenticationEntryPoint(authenticationEntryPoint).//匿名用户访问无权限资源时的异常处理
//                //会话管理
//                        and().sessionManagement().
//                maximumSessions(1).//同一账号同时登录最大用户数
//                expiredSessionStrategy(sessionInformationExpiredStrategy);//会话失效(账号被挤下线)处理逻辑
//        http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);//增加到默认拦截链中
//    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable();
        // 配置登录注销路径
        http.formLogin()
                .loginProcessingUrl("/login")
                .successHandler(authenticationSuccessHandler)
                .failureHandler(authenticationFailureHandler)
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(logoutSuccessHandler);
        // 配置路由权限信息
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
                        fsi.setSecurityMetadataSource(securityMetadataSource);
                        fsi.setAccessDecisionManager(accessDecisionManager);
                        return fsi;
                    }
                })
                .anyRequest().permitAll()
                .and()
                // 关闭跨站请求防护
                .csrf().disable().exceptionHandling()
                // 未登录处理
                .authenticationEntryPoint(authenticationEntryPoint)
                // 权限不足处理
                .accessDeniedHandler(accessDeniedHandler)
                .and()
                .sessionManagement()
                .maximumSessions(20)
                .sessionRegistry(sessionRegistry());
    }

}