Spring security 提供了接口,依据这些接口可以自定义自己的校验规则。
AccessDecisionManager 权限校验
FilterInvocationSecurityMetadataSource 权限配置数据库加载
AbstractSecurityInterceptor Spring security 核心抽象接口
AuthenticationManager 自定义用户角色数据
WebSecurityConfigurerAdapter Spring security核心配置
目前需求场景:cas第三方登录,返回cookies,然后微服务后台负责将用户信息放到缓存中去。不使用spring security的登陆。通过拦截直接将用户信息放到SecurityContextHolder.getContext() 中。然后通过 AbstractSecurityInterceptor 去组合数据库加载配置FilterInvocationSecurityMetadataSource 和自定义校验 AccessDecisionManager
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//配置白名单
.antMatchers("/swagger*","/webjars/**","/v2/**","/metrics").permitAll()
.antMatchers("/api/claims/**").permitAll()
//这些接口访问需要登录
.antMatchers("/api/**").authenticated()
// .access("hasRole('ROLE_R_ICORE_AIMS_NEWS_CONFIG')")
.and()
.csrf().disable();
//拦截和校验请求
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
}
}
拦截和校验,由于是微服务,当用户请求到某个微服务,需要确保当前微服务拥有该用户信息,然后在使用自定义增加配置和校验
1)查询一下当前用户,如果当前用户是默认用户,则添加用户信息到SecurityContextHolder.getContext()中
2)使用自定义校验
@Service
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
private Logger log = LoggerFactory.getLogger(MyFilterSecurityInterceptor.class);
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
//针对某些接口放白名单
private String[] ignoreStartUris = new String[]{
"/swagger",
"/webjars/",
"/v2/",
"/api/claims/",
"/metrics"
};
//设置自定义校验
@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 {
//通过cookie获取用户信息
HttpServletRequest request = fi.getHttpRequest();
String url = request.getServletPath();
if(StringUtils.startsWithAny(url, ignoreStartUris)){
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
return;
}
// SecurityContextHolder.clearContext();
String principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
log.info("[权限拦截] 当前登录用户 user:{}",principal);
log.info("[权限拦截] 当前用户角色:{}", JSON.toJSON( SecurityContextHolder.getContext().getAuthentication().getAuthorities()));
if("anonymousUser".equals(principal)){
log.info("[权限拦截] 给用户授权开始========url:{}",url);
Cookie[] cookies = request.getCookies();
String sessionId = null;
if(cookies!=null){
for (Cookie cookie : cookies) {
if (cookie.getName().equals(Constants.CAS_SESSION_ID)) {
sessionId = cookie.getValue();
break;
}
}
}
SessionUser sessionUser= UserUtils.getUserBySessionId(sessionId);
if(sessionUser!=null){
Authentication request1 = new UsernamePasswordAuthenticationToken(sessionUser.getUid(), sessionUser.getUid());
SampleAuthenticationManager am = new SampleAuthenticationManager();
am.setRoles(sessionUser.getRoles());
Authentication result = am.authenticate(request1);
SecurityContextHolder.getContext().setAuthentication(result);
}else {
log.info("[权限校验] 当前用户未登录");
}
log.info("[权限拦截] 当前用户角色:{}", JSON.toJSON( SecurityContextHolder.getContext().getAuthentication().getAuthorities()));
log.info("[权限拦截] 给用户授权结束========");
}
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(fi);
if (token==null){
return;
}
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
}
//设置自定义数据库配置
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
}
AccessDecisionManager 权限校验
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
// decide 方法是判定是否拥有权限的决策方法,
//authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
//object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
//configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public void decide(Authentication authentication,
Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
if(null== configAttributes || configAttributes.size() <=0) {
return;
}
ConfigAttribute c;
String needRole;
for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
c = iter.next();
needRole = c.getAttribute();
for(GrantedAuthority ga : authentication.getAuthorities()) {
//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
if(needRole.trim().equals(ga.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("no right");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
FilterInvocationSecurityMetadataSource 权限配置数据库加载
@Service
public class MyInvocationSecurityMetadataSourceService implements
FilterInvocationSecurityMetadataSource {
@Autowired
private PermissionDao permissionDao;
private HashMap<String, Collection<ConfigAttribute>> map =null;
/**
* 加载权限表中所有权限
*/
public void loadResourceDefine(){
map = new HashMap<>();
ConfigAttribute cfg;
List<AimsPermission> permissions = permissionDao.findAll();
for(AimsPermission permission : permissions) {
cfg = new SecurityConfig(permission.getName());
//此处只添加了用户的名字,其实还可以添加更多权限的信息,
// 例如请求方法到ConfigAttribute的集合中去。此处添加的信息将会作为MyAccessDecisionManager类的decide的第三个参数。
//用权限的getUrl() 作为map的key,用ConfigAttribute的集合作为 value,
setMap(permission.getUrl(),cfg);
}
}
/**
* 增加权限队列
* @param url
* @param cfg
*/
private void setMap(String url,ConfigAttribute cfg){
Collection<ConfigAttribute> array=map.get(url);
if(CollectionUtils.isEmpty(array)){
array = new ArrayList<>(6);
}
array.add(cfg);
map.put(url, array);
}
/**
* 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,
// 则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
if(map ==null) {
loadResourceDefine();
}
//object 中包含用户请求的request 信息
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
AntPathRequestMatcher matcher;
String resUrl;
for(Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) {
resUrl = iter.next();
matcher = new AntPathRequestMatcher(resUrl);
if(matcher.matches(request)) {
return map.get(resUrl);
}
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
AuthenticationManager 自定义用户角色数据
public class SampleAuthenticationManager implements AuthenticationManager {
List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();
private String prd="ROLE_";
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
private List<String> roles;
@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.getName().equals(auth.getCredentials())) {
for (String role:roles) {
//增加用户角色
AUTHORITIES.add(new SimpleGrantedAuthority(prd+role));
}
return new UsernamePasswordAuthenticationToken(auth.getName(),
auth.getCredentials(), AUTHORITIES);
}
throw new BadCredentialsException("Bad Credentials");
}
}
模拟数据库获取数据,可以具体到每个url对应的可以访问角色。
@Component
public class PermissionDao {
public List<AimsPermission> findAll(){
List<AimsPermission> list=new ArrayList<>(2);
AimsPermission aimsPermission1=new AimsPermission();
aimsPermission1.setName("ROLE_R_ICORE_AIMS_NEWS_CONFIG");
aimsPermission1.setUrl("/api/helper/queryByNameAndIdentityNo");
list.add(aimsPermission1);
AimsPermission aimsPermission=new AimsPermission();
aimsPermission.setName("ROLE_R_ICORE_AIMS_CHECK");
aimsPermission.setUrl("/api/helper/queryByNameAndIdentityNo");
AimsPermission aimsPermission2=new AimsPermission();
aimsPermission2.setName("ROLE_R_ICORE_AIMS_CHECK");
aimsPermission2.setUrl("/api/aimsinUser/getCurrentUserInfo");
list.add(aimsPermission2);
return list;
}
}