【问题标题】:Role access in Custom Spring authentication自定义 Spring 身份验证中的角色访问
【发布时间】:2017-01-01 20:13:58
【问题描述】:

我正在尝试使用 Spring Security 来保护我的 Rest API。 所以我的要求是用户应该通过 api 调用在标头中传递一个 apiKey,它将得到验证 w.r.t 到预定义的凭据。

假设我有 apikey:'ABCdEfG' 角色:'ROLE_ADMIN'

所以我编写了安全过滤器和身份验证提供程序的自定义实现。 apiKey 的身份验证工作正常,但它不是验证特定 api 所需的角色。

即如果没有 apiKey,我无法访问我的 api,但它无法验证所需的角色。

我当前的实现如下:

如果我在某处做错了,请告诉我。

应用上下文:

<security:global-method-security
    pre-post-annotations="enabled" />

<security:http entry-point-ref="authenticationEntryPoint"
    create-session="stateless">
    <security:intercept-url pattern="/api/*"
                access="ROLE_ADMIN" />
    <security:custom-filter before="FORM_LOGIN_FILTER"
        ref="restAuthenticationFilter" />
</security:http>

<bean id="restAuthenticationFilter"
    class="com.myapp.authentication.RestAuthenticationFilter2">
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />
</bean>

<bean class="com.myapp.authentication.RestAuthenticationEntryPoint"
    id="authenticationEntryPoint"></bean>
<bean
    class="com.myapp.authentication.RestAuthenticationSuccessHandler"
    id="authenticationSuccessHandler"></bean>
<bean class="com.myapp.authentication.CustomAuthenticationProvider"
    id="customAuthenticationProvider"></bean>


<bean class="com.myapp.authentication.util.UserAuthenticationDAO"
    factory-method="getInstance" id="userAuthenticationDAO"></bean>
<security:authentication-manager alias="authenticationManager">
    <security:authentication-provider
        ref="customAuthenticationProvider" />
</security:authentication-manager>

角色.java

import org.springframework.security.core.GrantedAuthority;

@SuppressWarnings("serial")
public class Role implements GrantedAuthority {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

}

用户.java

import java.util.List;

import org.springframework.security.core.userdetails.UserDetails;

@SuppressWarnings("serial")
public class User implements UserDetails {

    private String apiKey;

    /* Spring Security related fields */
    private List<Role> authorities;
    private boolean accountNonExpired = true;
    private boolean accountNonLocked = true;
    private boolean credentialsNonExpired = true;
    private boolean enabled = true;

    public String getApiKey() {
        return apiKey;
    }

    public void setApiKey(String apiKey) {
        this.apiKey = apiKey;
    }

    public List<Role> getAuthorities() {
        return authorities;
    }

    public void setAuthorities(List<Role> authorities) {
        this.authorities = authorities;
    }

    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }

    public void setAccountNonExpired(boolean accountNonExpired) {
        this.accountNonExpired = accountNonExpired;
    }

    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }

    public void setAccountNonLocked(boolean accountNonLocked) {
        this.accountNonLocked = accountNonLocked;
    }

    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    public void setCredentialsNonExpired(boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    @Override
    public String getPassword() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getUsername() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public boolean equals(Object obj) {
        return this.apiKey.equals(((User) obj).getApiKey());
    }
}

CustomAuthentiCationToken.java

    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String token;

    public CustomAuthenticationToken(String token) {
        super(null, null);
        this.token = token;
    }

    public String getToken() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return null;
    } }

身份验证过滤器

    import java.io.IOException;

    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import org.springframework.security.authentication.BadCredentialsException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;

    import com.myapp.authentication.bean.CustomAuthenticationToken;

    public class RestAuthenticationFilter2 extends AbstractAuthenticationProcessingFilter {
        protected RestAuthenticationFilter2() {
            super("/**");
        }

        @Override
        protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
            return true;
        }

        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
                throws AuthenticationException {

            String header = request.getHeader("Authorization");

            if (header == null) {
                throw new BadCredentialsException("No token found in request headers");
            }

            //String authToken = header.substring(7);
            String authToken = header.trim();

            CustomAuthenticationToken authRequest = new CustomAuthenticationToken(authToken);
            return getAuthenticationManager().authenticate(authRequest);
        }

        @Override
        protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                FilterChain chain, Authentication authResult) throws IOException, ServletException {
            super.successfulAuthentication(request, response, chain, authResult);

            // As this authentication is in HTTP header, after success we need to
            // continue the request normally
            // and return the response as if the resource was not secured at all
            chain.doFilter(request, response);
        }
    }

身份验证提供者

public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Autowired
    RetinaAuthenticationService retinaAuthenticationService;

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // TODO Auto-generated method stub

    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        CustomAuthenticationToken customAuthenticationToken = (CustomAuthenticationToken) authentication;
        String token = customAuthenticationToken.getToken();

        User user = retinaAuthenticationService.loadUserByApiKey(token);

        if (null != user) {
            return user;
        } else {
            throw new BadCredentialsException("API token is not valid");
        }
    }

}

【问题讨论】:

  • 对不起,我真的不明白是哪个问题。 apikey 得到验证,然后,会发生什么,请求的角色没有被授予?
  • apiKey 在没有检查角色的情况下得到验证。无论我在intercept-url 中定义什么角色,它仍然适用于apiKey。
  • 你请求的网址怎么样?
  • 我正在通过 Post 客户端请求 URL,我在其中为 apiKey 添加了额外的标头参数。
  • 我的意思是你要什么网址。

标签: java spring spring-security


【解决方案1】:

根据你写的安全配置

<security:http entry-point-ref="authenticationEntryPoint"
    create-session="stateless">
    <security:intercept-url pattern="/api/*"
                access="ROLE_ADMIN" />
    <security:custom-filter before="FORM_LOGIN_FILTER"
        ref="restAuthenticationFilter" />
</security:http>

您声明对 /api/* 的任何传入请求(这意味着 http://localhost:8080/myapp/api/test 将受到保护,但 http://localhost:8080/myapp/api/http://localhost:8080/myapp/api/more/test 均不受保护)必须具有 ROLE_ADMIN 作为授予权限。

当您将 create-session 设置为无状态时,任何请求都必须经过验证,因此您必须在每个请求中包含身份验证凭据(在本例中为 APIKEY)。

一旦 APIKEY 被验证(因此请求被验证),那么它将检查您的 CustomAuthenticationProvider 返回的 Authentication 实例是否具有 ROLE_ADMIN 作为授予的权限。但你不必自己检查,spring-security过滤器链(org.springframework.web.filter.DelegatingFilterProxy)会自己做。

所以,不需要自己去访问你在security:intercept-url元素的access属性中配置的权限。

这最终意味着,如果提供者返回的User对象在List authority中具有ROLE_ADMIN作为权限,则允许访问端点/api/test,否则不允许。

编辑:我很生气,所以我通过复制您发布的类并构建其他东西来测试您的配置。

我构建了一个像这样的 RetinaAuthenticationService 的固定实现,就像剩下的一样,基于带有方法 loadUserByApikey() 的接口:

public interface RetinaAuthenticationService {

    public abstract User loadUserByApiKey(String token);

}

实现:

public class RetinaAuthenticationServiceImpl implements RetinaAuthenticationService {

    private Map<String, List<String>> apiKeyRoleMappings;

    @Override
    public User loadUserByApiKey(String token) {
        User user = null;
        if(this.apiKeyRoleMappings.containsKey(token)){
            user = new User();
            user.setApiKey(token);
            List<Role> authorities = new ArrayList<Role>();
            for(String roleStr : this.apiKeyRoleMappings.get(token)){
                Role role = new Role();
                role.setName(roleStr);
                authorities.add(role);
            }
            user.setAuthorities(authorities );
            user.setAccountNonExpired(true);
            user.setAccountNonLocked(true);
            user.setCredentialsNonExpired(true);
            user.setEnabled(true);
        }else{
            throw new BadCredentialsException("ApiKey " + token + " not found");
        }
        return user;
    }

    public Map<String, List<String>> getApiKeyRoleMappings() {
        return apiKeyRoleMappings;
    }

    public void setApiKeyRoleMappings(Map<String, List<String>> apiKeyRoleMappings) {
        this.apiKeyRoleMappings = apiKeyRoleMappings;
    }


}

然后我在一个正在运行的项目中配置了所有的 securiy-context.xml 用于测试目的:

<security:http auto-config='false' pattern="/api/**" entry-point-ref="serviceAccessDeniedHandler" create-session="stateless" use-expressions="false">
    <security:intercept-url pattern="/api/*" access="ROLE_ADMIN" />
    <security:intercept-url pattern="/api/user/*" access="ROLE_USER,ROLE_ADMIN" />
    <security:custom-filter before="FORM_LOGIN_FILTER" ref="restAuthenticationFilter" />
    <security:csrf disabled="true"/>
</security:http>

<beans:bean id="restAuthenticationFilter"
    class="com.eej.test.security.filter.RestAuthenticationFilter2">
    <beans:property name="authenticationManager" ref="apiAuthenticationManager" />
    <beans:property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />
</beans:bean>

<beans:bean id="retinaAuthenticationServiceImpl" class="com.eej.test.security.services.RetinaAuthenticationServiceImpl">
    <beans:property name="apiKeyRoleMappings">
        <beans:map>
            <beans:entry key="aaaaa">
                <beans:list>
                    <beans:value>ROLE_USER</beans:value>
                </beans:list>
            </beans:entry>
            <beans:entry key="bbbbb">
                <beans:list>
                    <beans:value>ROLE_ADMIN</beans:value>
                </beans:list>
            </beans:entry>
            <beans:entry key="ccccc">
                <beans:list>
                    <beans:value>ROLE_USER</beans:value>
                    <beans:value>ROLE_ADMIN</beans:value>
                </beans:list>
            </beans:entry>
        </beans:map>
    </beans:property>
</beans:bean>

<!-- bean class="com.myapp.authentication.RestAuthenticationEntryPoint"  id="authenticationEntryPoint"></bean-->
<beans:bean
    class="com.eej.test.security.handler.RestAuthenticationSuccessHandler" id="authenticationSuccessHandler" />
<beans:bean class="com.eej.test.security.CustomAuthenticationProvider" id="customAuthenticationProvider" />

<!-- beans:bean class="com.myapp.authentication.util.UserAuthenticationDAO" factory-method="getInstance" id="userAuthenticationDAO" /-->
<security:authentication-manager alias="apiAuthenticationManager">
    <security:authentication-provider ref="customAuthenticationProvider" />
</security:authentication-manager>  

我对你的做了一些小改动(使用预先存在的入口点 ref,将模式应用于 security:http 部分,因为我在这个项目中已经有一个通用的,将 use-expressions 设置为 false,禁用自动配置并禁用 csrf),更改包名并注释不必要的元素

我必须为我的类 RetinaAuthenticationServiceImpl 配置一个 bean, 我用这个 apikey-role 映射设置了一个地图:

  • aaaaa > ROLE_USER
  • bbbbb > ROLE_ADMIN
  • ccccc > ROLE_USER,ROLE_ADMIN

一切正常。访问 http://host:port/context/api/test 返回 200,其中使用令牌 bbbbbccccc 和 403,同时使用 aaaaa

【讨论】:

  • 好的,谢谢@jlumietu,但在我的实现中,这个角色不起作用。就像我的用户列表中有 user'ROLE_USER' 并且配置说 /api* 应该只为 'ROLE_ADMIN' 访问,但我仍然可以访问带有 ROLE_USER 的用户的 api,理想情况下它会说 UNAUTHENTICATED。可能我觉得我在 CustomAuthenticationToken 类中做错了。你对此有什么想法吗?
  • 澄清一下,您确定您的过滤器等在接收请求时正在执行吗?记得在 web.xml 中配置 springSecurityFilterChain 吗?
  • 是的,我在自定义过滤器上进行了调试,它正在执行,我还在 web.xml 中定义了 springSecurityFilterChain。
  • springSecurityFilterChainorg.springframework.web.filter.DelegatingFilterProxyspringSecurityFilterChain/*
  • 请看我的编辑;我在我的测试环境中构建并工作
猜你喜欢
  • 2017-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-13
  • 2021-02-21
  • 2010-10-29
  • 2012-04-10
  • 2014-04-20
相关资源
最近更新 更多