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, CollectionconfigAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if(null== configAttributes || configAttributes.size() <=0) { return; } ConfigAttribute c; String needRole; for(Iterator 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> map =null; /** * 加载权限表中所有权限 */ public void loadResourceDefine(){ map = new HashMap<>(); ConfigAttribute cfg; List 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 array=map.get(url); if(CollectionUtils.isEmpty(array)){ array = new ArrayList<>(6); } array.add(cfg); map.put(url, array); } /** * 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中, // 则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。 */ @Override public Collection getAttributes(Object object) throws IllegalArgumentException { if(map ==null) { loadResourceDefine(); } //object 中包含用户请求的request 信息 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); AntPathRequestMatcher matcher; String resUrl; for(Iterator 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 getAllConfigAttributes() { return null; } @Override public boolean supports(Class> clazz) { return true; } }
AuthenticationManager 自定义用户角色数据
public class SampleAuthenticationManager implements AuthenticationManager { ListAUTHORITIES = new ArrayList (); private String prd="ROLE_"; public List getRoles() { return roles; } public void setRoles(List roles) { this.roles = roles; } private List 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 ListfindAll(){ List 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; } }