spring security自定义权限配置

本文详细介绍Spring Security的权限管理实现,包括用户身份认证、资源权限分配、决策管理及自定义实现。通过自定义MUserDetailsService实现UserDetailsService接口进行用户认证,使用MyAccessDecisionManager判断用户访问权限。

    最近在项目中遇到了关于spring security的问题,所以学习一下。

需要引入依赖:

       <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>4.2.3.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>4.2.3.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

用户身份认证:

自定义一个实现类MUserDetailsService来实现UserDetailsService接口。

其中需要实现一个loadUserByUsername方法,用来读取用户的角色。
在这里需要从数据库中通过用户名来查询用户的信息和用户所属的角色
其中MGrantedAuthority实现了GrantedAuthority接口,用于构建用户权限。
MUserDeatils实现了UserDeatils接口,用于存放用户信息与权限。

UserDetailsService在身份认证中的作用:

å¾çæè¿°

    Spring Security中进行身份验证的是AuthenticationManager接口,ProviderManager是它的一个默认实现,但它并不用来处理身份认证,而是委托给配置好的AuthenticationProvider,每个AuthenticationProvider会轮流检查身份认证。检查后或者返回Authentication对象或者抛出异常。验证身份就是加载响应的UserDetails,看看是否和用户输入的账号、密码、权限等信息匹配。此步骤由实现AuthenticationProvider的DaoAuthenticationProvider(它利用UserDetailsService验证用户名、密码和授权)处理。包含 GrantedAuthority 的 UserDetails对象在构建 Authentication对象时填入数据。

CasUserDetailService.class中的loadUserByUsername方法 

@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询用户信息
        AdminUser adminUser = adminUserService.getUserByLoginName(username);

        if (adminUser == null) {
            throw new UsernameNotFoundException("不是新应用管理平台的用户!请联系管理员添加权限!");
        }
        //构建权限
        Collection<GrantedAuthority> grantedAuthorities = new HashSet<>();

        // 每位同学都默认拥有的角色
        grantedAuthorities.add(new SimpleGrantedAuthority(SecurityConstant.ROLE_COMMON));
        //查询用户角色
        List<AdminUserRole> roles = this.adminUserService.getUserRoles(adminUser.getId());

        if (CollectionUtils.isNotEmpty(roles)) {
            for (AdminUserRole ur : roles) {
                GrantedAuthority ga = new SimpleGrantedAuthority(SecurityConstant.ROLE_NAME_PREFIX + ur.getRoleId());
                grantedAuthorities.add(ga);
            }
        }

        return new User(adminUser.getUserName(), StringUtils.EMPTY, grantedAuthorities);
    }

SimpleGrantedAuthority.class

public final class SimpleGrantedAuthority implements GrantedAuthority {
    private static final long serialVersionUID = 420L;
    private final String role;

    public SimpleGrantedAuthority(String role) {
        Assert.hasText(role, "A granted authority textual representation is required");
        this.role = role;
    }

    public String getAuthority() {
        return this.role;
    }

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else {
            return obj instanceof SimpleGrantedAuthority ? this.role.equals(((SimpleGrantedAuthority)obj).role) : false;
        }
    }

    public int hashCode() {
        return this.role.hashCode();
    }

    public String toString() {
        return this.role;
    }
}

CasUserDeatils.class

实现UserDetails接口定义好变量。

读取资源与所属角色

需要自定义实现类实现FilterInvocationSecurityMetadataSource接口。通过loadResourceDefine方法可以实现资源与权限的对应关系。

要使我们自定义的MFilterInvocationSecurityMetadataSource生效,我们还需要定义一个MyFilterSecurityInterceptor类。
这里的数据需要从数据库中取得。另外自定义接口UrlMatcher,实现类为AntUrlPathMatcher。

MFilterInvocationSecurityMetadataSource.class

@Component
    public class MFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired
    public IRescAndRoleService iRescAndRoleService ;
    @Autowired
    private IUserService iUserService ;
    private UrlMatcher urlMatcher = new AntUrlPathMatcher();
    // 资源权限集合
    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
    public void loadResourceDefine(){
        resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
        //取得用户信息
        List<User> userList = iUserService.query();
       //取得资源与角色列表
        List<RescAndRole> resourceList = iRescAndRoleService.query();
        System.out.println(resourceList);
        for (RescAndRole resource : resourceList) {
            Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
            atts.add(new SecurityConfig(resource.getRoleName() ));
            resourceMap.put(resource.getResString(), atts);
        }
    }
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        loadResourceDefine();//防止无法注入问题
        // guess object is a URL.
        String url = ((FilterInvocation) o).getRequestUrl();
        Iterator<String> ite = resourceMap.keySet().iterator();
        while (ite.hasNext()) {
            String resURL = ite.next();
            if (urlMatcher.pathMatchesUrl(resURL, url)) {
                return resourceMap.get(resURL);
            }
        }
        return null;
    }
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }
    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

AntUrlPathMatcher.class

public class AntUrlPathMatcher implements UrlMatcher {

    private boolean requiresLowerCaseUrl;
    private PathMatcher pathMatcher;

    public AntUrlPathMatcher() {
        this(true);
    }

    public AntUrlPathMatcher(boolean requiresLowerCaseUrl) {
        this.requiresLowerCaseUrl = true;
        this.pathMatcher = new AntPathMatcher();

        this.requiresLowerCaseUrl = requiresLowerCaseUrl;
    }

    public Object compile(String path) {
        if (this.requiresLowerCaseUrl) {
            return path.toLowerCase();
        }
        return path;
    }

    public void setRequiresLowerCaseUrl(boolean requiresLowerCaseUrl) {
        this.requiresLowerCaseUrl = requiresLowerCaseUrl;
    }

    public boolean pathMatchesUrl(Object path, String url) {
        if (("/**".equals(path)) || ("**".equals(path))) {
            return true;
        }
        return this.pathMatcher.match((String) path, url);
    }

    public String getUniversalMatchPattern() {
        return "/**";
    }

    public boolean requiresLowerCaseUrl() {
        return this.requiresLowerCaseUrl;
    }

    public String toString() {
        return super.getClass().getName() + "[requiresLowerCase='"
                + this.requiresLowerCaseUrl + "']";
    }
}

MyFilterSecurityInterceptor.class

public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor
        implements Filter {
    private FilterInvocationSecurityMetadataSource securityMetadataSource;
    
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }

    public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    public Class<? extends Object> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    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);
        }
    }

    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    public void setSecurityMetadataSource(
            FilterInvocationSecurityMetadataSource newSource) {
        this.securityMetadataSource = newSource;
    }

    public void destroy() {
    }

    public void init(FilterConfig arg0) throws ServletException {
    }

}

决策管理器

自定义一个决策管理器MyAccessDecisionManager实现AccessDecisionManager接口。其中的decide方法,决定某一个用户是否有权限访问某个url
/* (non-Javadoc)
     * @see org.springframework.security.access.AccessDecisionManager#decide(org.springframework.security.core.Authentication, java.lang.Object, java.util.Collection)
     * 该方法决定该权限是否有权限访问该资源,其实object就是一个资源的地址,authentication是当前用户的
     * 对应权限,如果没登陆就为游客,登陆了就是该用户对应的权限
     */
    @Override
    public void decide(Authentication authentication, Object object,
                       Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        if(configAttributes == null) {
            return;
        }
        System.out.println(object.toString()); // object is a URL.
        //所请求的资源拥有的权限(一个资源对多个权限)
        Iterator<ConfigAttribute> iterator = configAttributes.iterator();
        while(iterator.hasNext()) {
            ConfigAttribute configAttribute = iterator.next();
            //访问所请求资源所需要的权限
            String needPermission = configAttribute.getAttribute();
            System.out.println("访问"+object.toString()+"需要的权限是:" + needPermission);
            //用户所拥有的权限authentication
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for(GrantedAuthority ga : authorities) {
                if(needPermission.equals(ga.getAuthority())) {
                    return;
                }
            }
        }
        //没有权限
        throw new AccessDeniedException(" 没有权限访问! ");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        // TODO Auto-generated method stub
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        // TODO Auto-generated method stub
        return true;
    }

配置XML

添加Seucrity的过滤器,将拦截所有资源访问

注意

    只能配置成 /*
    
    <!--加载Security配置文件与mybatis配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            WEB-INF/config/security.xml
            WEB-INF/config/spring-mybatis.xml
        </param-value>
    </context-param>
    
    <!-- spring security 的过滤器配置 -->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

spring-security.xml

<b:beans xmlns="http://www.springframework.org/schema/security"
         xmlns:b="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">


    <!--登陆页面不验证-->
    <http pattern="/userLogin.html" security="none" />
    <!--静态文件请求不验证-->
    <http pattern="/js/**" security="none" />
    <http pattern="/css/**" security="none" />
    <!--restful请求-->
    <http pattern="/login" security="none" />
    <http pattern="/getGrid" security="none" />
    <!--浏览器会自动请求网站图标:favicon.ico -不验证  -->
    <http pattern="/favicon.ico" security="none" />
    <http >

        <!--自定义权限不足时显示的页面-->
        <access-denied-handler error-page="/accessHint.html"></access-denied-handler>
        <!-- 自定义登录界面 -->
        <form-login
                authentication-failure-url="/userLogin.html?error=true"
                login-page="/userLogin.html"
                default-target-url="/index.html"
                login-processing-url="/j_spring_security_check" />
        <logout invalidate-session="true"
                logout-success-url="/userLogin.html"
                logout-url="/j_spring_security_logout"/>
        <!-- 通过配置custom-filter来增加过滤器,before="FILTER_SECURITY_INTERCEPTOR"表示在SpringSecurity默认的过滤器之前执行。 -->
        <custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" />
        <csrf disabled="true" />

    </http>

    <!-- 认证过滤器 -->
    <b:bean id="filterSecurityInterceptor"
                class="com.hand.security.utils.MyFilterSecurityInterceptor">
        <b:property name="rejectPublicInvocations" value="true"/>
        <!-- 用户拥有的权限 -->
        <b:property name="accessDecisionManager" ref="accessDecisionManager" />
        <!-- 用户是否拥有所请求资源的权限 -->
        <b:property name="authenticationManager" ref="authenticationManager" />
        <!-- 资源与权限对应关系 -->
        <b:property name="securityMetadataSource" ref="securityMetadataSource" />
    </b:bean>

    <!-- 2、更改验证信息加载方式 -->
    <authentication-manager alias="authenticationManager">
        <authentication-provider
                user-service-ref="mUserDetailsService">
            <!--如果用户的密码采用加密的话 <password-encoder hash="md5" /> -->
        </authentication-provider>
    </authentication-manager>

    <!-- 1、配置自定义类MUserDetailsService -->
    <b:bean id="mUserDetailsService" class="com.hand.security.service.impl.MUserDetailsService" />

    <!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
    <b:bean id="accessDecisionManager" class="com.hand.security.utils.MyAccessDecisionManager"></b:bean>

    <!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问 -->
    <b:bean id="securityMetadataSource" class="com.hand.security.utils.MFilterInvocationSecurityMetadataSource" ></b:bean>

</b:beans>

 

参考:https://segmentfault.com/a/1190000010232638 

Spring Security中,如果你想要测试自定义的`SecurityConfig`,首先你需要创建一个模拟的`WebSecurityConfigurerAdapter`或者使用`@Configuration`注解的类来替代默认的配置。然后,在`ActsSecComponentTest`中,你可以按照以下步骤进行配置: 1. **创建测试类**:继承`AbstractSecurityWebApplicationTests`或者自定义的测试类,这将设置基础的安全上下文。 ```java @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class CustomSecurityConfigTest extends AbstractSecurityWebApplicationTests { // ... } ``` 2. **Mocking或替换依赖**:为了隔离测试,你需要mock掉`UserDetailsService`等对实际业务有影响的服务。可以使用`Mockito`库来创建mock对象。 ```java @Autowired private UserDetailsService userDetailsService; // 在测试开始前替换为mock beforeEach(() -> when(userDetailsService.loadUserByUsername(anyString())).thenReturn(mock(User.class))); ``` 3. **配置测试Security Config**:在测试类中,配置你的自定义`SecurityConfig`,覆盖需要测试的部分。 ```java @Configuration @EnableWebSecurity public class CustomSecurityConfig extends WebSecurityConfigurerAdapter { // ... 自定义security配置 @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // 添加你的authenticator规则 auth.inMemoryAuthentication().withUser("user").password(passwordEncoder().encode("password")).roles("USER"); } // 如果有其他的@Bean配置,也在这里提供它们 } @Override protected void configure(HttpSecurity http) throws Exception { // 自定义http请求的处理逻辑 http.authorizeRequests() .antMatchers("/secured").hasRole("USER") .anyRequest().authenticated(); } ``` 4. **编写测试方法**:现在你可以编写测试方法来验证你的安全配置是否按预期工作,例如尝试访问受保护的资源。 ```java @Test public void testSecuredEndpoint() { // 登录并获取认证后的http client Authentication authentication = usernamePasswordAuthenticationToken("user", "password").getPrincipal(); SecurityContext securityContext = new SecurityContextImpl(authentication); SecurityContextHolder.getContext().setAuthentication(securityContext); // 执行请求 MockMvc mockMvc = webAppSetupMockMvc(getWebApplicationContext()); mockMvc.perform(get("/secured")) .andExpect(status().isForbidden()); // 或者其他预期状态码 } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值