package com.liuyanzhao.sens.config.security;
import com.liuyanzhao.sens.common.utils.SecurityUtil;
import com.liuyanzhao.sens.config.security.jwt.AuthenticationFailHandler;
import com.liuyanzhao.sens.config.security.jwt.AuthenticationSuccessHandler;
import com.liuyanzhao.sens.config.security.jwt.JWTAuthenticationFilter;
import com.liuyanzhao.sens.config.security.jwt.RestAccessDeniedHandler;
import com.liuyanzhao.sens.config.security.permission.MyFilterSecurityInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
/**
- Security 核心配置类
- 开启注解控制权限至Controller
- @author 言曌
*/
@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private IgnoredUrlsProperties ignoredUrlsProperties;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private AuthenticationSuccessHandler successHandler;
@Autowired
private AuthenticationFailHandler failHandler;
@Autowired
private RestAccessDeniedHandler accessDeniedHandler;
@Autowired
private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private SecurityUtil securityUtil;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = http
.authorizeRequests();
//除配置文件忽略路径其它所有请求都需经过认证和授权
for(String url:ignoredUrlsProperties.getUrls()){
registry.antMatchers(url).permitAll();
}
registry.and()
//表单登录方式
.formLogin()
.loginPage("/sens/common/needLogin")
//登录请求url
.loginProcessingUrl("/sens/login")
.permitAll()
//成功处理类
.successHandler(successHandler)
//失败
.failureHandler(failHandler)
.and()
//允许网页iframe
.headers().frameOptions().disable()
.and()
.logout()
.permitAll()
.and()
.authorizeRequests()
//任何请求
.anyRequest()
//需要身份认证
.authenticated()
.and()
//关闭跨站请求防护
.csrf().disable()
//前后端分离采用JWT 不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//自定义权限拒绝处理类
.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
.and() //添加自定义权限过滤器
.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
}
}
addFilterBefore 作用就是当执行权限验证前执行,我们需要在这之前判断即可。
2.自定义拦截器
MyFilterSecurityInterceptor.java
package com.liuyanzhao.sens.config.security.permission;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
/**
- 权限管理拦截器
- 监控用户行为
- @author 言曌
*/
@Slf4j
@Component
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}
3.自定义权限资源管理器
MySecurityMetadataSource.java
package com.liuyanzhao.sens.config.security.permission;
import com.liuyanzhao.sens.common.constant.CommonConstant;
import com.liuyanzhao.sens.modules.base.entity.Permission;
import com.liuyanzhao.sens.modules.base.service.PermissionService;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
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 org.springframework.util.PathMatcher;
import java.util.*;
/**
- 权限资源管理器
- 为权限决断器提供支持
- @author 言曌
/
@Slf4j
@Component
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
private PermissionService permissionService;
private Map<String, Collection> map = null;
/*- 加载权限表中所有操作请求权限
/
public void loadResourceDefine() {
map = new HashMap<>(16);
Collection configAttributes;
ConfigAttribute cfg;
// 获取启用的权限操作请求
List permissions = permissionService.findByTypeAndStatusOrderBySortOrder(CommonConstant.PERMISSION_OPERATION, CommonConstant.STATUS_NORMAL);
for (Permission permission : permissions) {
if (StrUtil.isNotBlank(permission.getTitle()) && StrUtil.isNotBlank(permission.getPath())) {
configAttributes = new ArrayList<>();
cfg = new SecurityConfig(permission.getTitle());
//作为MyAccessDecisionManager类的decide的第三个参数
configAttributes.add(cfg);
//用权限的path作为map的key,用ConfigAttribute的集合作为value
map.put(permission.getPath(), configAttributes);
}
}
}
/* - 判定用户请求的url是否在权限表中
- 如果在权限表中,则返回给decide方法,用来判定用户是否有此权限
- 如果不在权限表中则放行
- @param o
- @return
- @throws IllegalArgumentException
*/
@Override
public Collection getAttributes(Object o) throws IllegalArgumentException {
if (map == null) {
loadResourceDefine();
}
//Object中包含用户请求request
String url = ((FilterInvocation) o).getRequestUrl();
PathMatcher pathMatcher = new AntPathMatcher();
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String resURL = iterator.next();
if (StrUtil.isNotBlank(resURL) && pathMatcher.match(resURL, url)) {
return map.get(resURL);
}
}
return null;
}
@Override
public Collection getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
- 加载权限表中所有操作请求权限
4.自定义角色决断器
MyAccessDecisionManager.java
package com.liuyanzhao.sens.config.security.permission;
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;
import java.util.Iterator;
/**
- 权限管理决断器
- 判断用户拥有的权限或角色是否有资源访问权限
- @author 言曌
*/
@Slf4j
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if(configAttributes==null){
return;
}
Iterator iterator = configAttributes.iterator();
while (iterator.hasNext()){
ConfigAttribute c = iterator.next();
String needPerm = c.getAttribute();
for(GrantedAuthority ga : authentication.getAuthorities()) {
// 匹配用户拥有的ga 和 系统中的needPerm
if(needPerm.trim().equals(ga.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException(“抱歉,您没有访问权限”);
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}