이 글은 주로 Java의 사용자 정의 Spring Security 권한 제어 관리 예제를 소개합니다(실습 글). 관심 있는 친구들이 참고할 수 있습니다.
배경 설명
프로젝트는 url + httpmethod와 같이 세부적인 권한 제어를 수행해야 합니다(예: http://www. php.cn/일부 역할은 보기(HTTP GET)만 가능하고 추가, 수정, 삭제(POST, PUT, DELETE) 권한은 없습니다.
테이블 디자인
의심을 피하기 위해 사용할 핵심 필드만 나열하고 나머지는 직접 파악하시기 바랍니다.
1.admin_user 관리자 사용자 테이블, 키 필드(id, role_id).
2.t_role 역할 테이블, 키 필드(id,privilege_id).
3.t_privilege 권한 테이블, 키 필드(id, url, method)
세 테이블의 관계는 더 말할 필요도 없이 한눈에 볼 수 있다. 필드 밖으로.
구현 전 분석
역으로 생각할 수 있습니다.
필요를 실현하기 위해 가장 중요한 단계는 Spring Security의 AccessDecisionManager가 판단하도록 하는 것입니다. 요청된 URL + httpmethod가 데이터베이스의 구성과 일치하는지 여부. 그러나 AccessDecisionManager는 유사한 요구 사항을 가진 관련 투표자를 결정하지 않습니다. 따라서 Voter 구현을 사용자 정의해야 합니다(기본적으로 등록된 AffirmativeBased 전략은 투표자가 ACCESS_GRANTED 투표를 하는 한 통과된 것으로 결정된다는 것입니다. 우리의 필요에 따라). voter 구현 후 주요 매개변수(Collection
단계 요약:
1. voter 구현을 사용자 정의합니다.
2. ConfigAttribute 구현을 사용자 정의합니다.
3. SecurityMetadataSource 구현을 사용자 정의합니다.
4. 인증에는 사용자 인스턴스가 포함됩니다(물론 모든 사람이 이미 이 작업을 수행했어야 함).
5. 🎜>
실전 전투 프로젝트
1. 맞춤형 GrantedAuthority 구현UrlGrantedAuthority.javapublic class UrlGrantedAuthority implements GrantedAuthority { private final String httpMethod; private final String url; public UrlGrantedAuthority(String httpMethod, String url) { this.httpMethod = httpMethod; this.url = url; } @Override public String getAuthority() { return url; } public String getHttpMethod() { return httpMethod; } public String getUrl() { return url; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UrlGrantedAuthority target = (UrlGrantedAuthority) o; if (httpMethod.equals(target.getHttpMethod()) && url.equals(target.getUrl())) return true; return false; } @Override public int hashCode() { int result = httpMethod != null ? httpMethod.hashCode() : 0; result = 31 * result + (url != null ? url.hashCode() : 0); return result; } }
public class SystemUser implements UserDetails { private final Admin admin; private List<MenuOutput> menuOutputList; private final List<GrantedAuthority> grantedAuthorities; public SystemUser(Admin admin, List<AdminPrivilege> grantedPrivileges, List<MenuOutput> menuOutputList) { this.admin = admin; this.grantedAuthorities = grantedPrivileges.stream().map(it -> { String method = it.getMethod() != null ? it.getMethod().getLabel() : null; return new UrlGrantedAuthority(method, it.getUrl()); }).collect(Collectors.toList()); this.menuOutputList = menuOutputList; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.grantedAuthorities; } @Override public String getPassword() { return admin.getPassword(); } @Override public String getUsername() { return null; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } public Long getId() { return admin.getId(); } public Admin getAdmin() { return admin; } public List<MenuOutput> getMenuOutputList() { return menuOutputList; } public String getSalt() { return admin.getSalt(); } }
public class UrlConfigAttribute implements ConfigAttribute { private final HttpServletRequest httpServletRequest; public UrlConfigAttribute(HttpServletRequest httpServletRequest) { this.httpServletRequest = httpServletRequest; } @Override public String getAttribute() { return null; } public HttpServletRequest getHttpServletRequest() { return httpServletRequest; } }
4. 맞춤형 SecurityMetadataSource 구현
public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { final HttpServletRequest request = ((FilterInvocation) object).getRequest(); Set<ConfigAttribute> allAttributes = new HashSet<>(); ConfigAttribute configAttribute = new UrlConfigAttribute(request); allAttributes.add(configAttribute); return allAttributes; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } }
5. 🎜>
public class UrlMatchVoter implements AccessDecisionVoter<Object> { @Override public boolean supports(ConfigAttribute attribute) { if (attribute instanceof UrlConfigAttribute) return true; return false; } @Override public boolean supports(Class<?> clazz) { return true; } @Override public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { if(authentication == null) { return ACCESS_DENIED; } Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); for (ConfigAttribute attribute : attributes) { if (!(attribute instanceof UrlConfigAttribute)) continue; UrlConfigAttribute urlConfigAttribute = (UrlConfigAttribute) attribute; for (GrantedAuthority authority : authorities) { if (!(authority instanceof UrlGrantedAuthority)) continue; UrlGrantedAuthority urlGrantedAuthority = (UrlGrantedAuthority) authority; if (StringUtils.isBlank(urlGrantedAuthority.getAuthority())) continue; //如果数据库的method字段为null,则默认为所有方法都支持 String httpMethod = StringUtils.isNotBlank(urlGrantedAuthority.getHttpMethod()) ? urlGrantedAuthority.getHttpMethod() : urlConfigAttribute.getHttpServletRequest().getMethod(); //用Spring已经实现的AntPathRequestMatcher进行匹配,这样我们数据库中的url也就支持ant风格的配置了(例如:/xxx/user/**) AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(urlGrantedAuthority.getAuthority(), httpMethod); if (antPathRequestMatcher.matches(urlConfigAttribute.getHttpServletRequest())) return ACCESS_GRANTED; } } return ACCESS_ABSTAIN; } }
public class UrlFilterSecurityInterceptor extends FilterSecurityInterceptor { public UrlFilterSecurityInterceptor() { super(); } @Override public void init(FilterConfig arg0) throws ServletException { super.init(arg0); } @Override public void destroy() { super.destroy(); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { super.doFilter(request, response, chain); } @Override public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return super.getSecurityMetadataSource(); } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return super.obtainSecurityMetadataSource(); } @Override public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) { super.setSecurityMetadataSource(newSource); } @Override public Class<?> getSecureObjectClass() { return super.getSecureObjectClass(); } @Override public void invoke(FilterInvocation fi) throws IOException, ServletException { super.invoke(fi); } @Override public boolean isObserveOncePerRequest() { return super.isObserveOncePerRequest(); } @Override public void setObserveOncePerRequest(boolean observeOncePerRequest) { super.setObserveOncePerRequest(observeOncePerRequest); } }
<security:http> ... <security:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" /> </security:http> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref="daoAuthenticationProvider"/> </security:authentication-manager> <bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased"> <constructor-arg> <list> <bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter" /> <bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter" /> <bean id="urlMatchVoter" class="com.mobisist.app.security.access.voter.UrlMatchVoter" /> </list> </constructor-arg> </bean> <bean id="securityMetadataSource" class="com.mobisist.app.security.access.UrlFilterInvocationSecurityMetadataSource" /> <bean id="filterSecurityInterceptor" class="com.mobisist.app.security.access.UrlFilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager"/> <property name="accessDecisionManager" ref="accessDecisionManager"/> <property name="securityMetadataSource" ref="securityMetadataSource" /> </bean>