SpringSecurity
使用过程中总结出来的比较全且规范的写法
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());
}
}