java - Spring Security + spring oauth2解析

标签 java spring security spring-mvc spring-security

我正在使用 spring 开发一个新的 REST 服务器。服务器必须只有服务器端逻辑,没有 js 或 View 。

目前我开始使用 Spring-boot 1.2.7 版本,但这只是为了在服务器设置完成之前为前端开发人员提供服务器。

之后我将更改为 spring-core version 4.1 或类似的版本。 目前我在配置安全部分时遇到困难。

一开始我是用Java Config配置的,后来改成xml了,因为我已经有类似的配置了。

我的最终结果必须是这些:

hostname **/api/auth**** :is the entry point where a frontend developer make a **POST request with username and password of a customer. This call returns a token. This token permits me to identify the user the next time.

hostname /api/secure/resource/1 : is a resource that is protected, and can be accessible only with a valid token

hostname /api/other/1 : is an other type of resource that is not protected and can be accessible for everyone hostname /api/secure/bambi/: is a resource that can be accessed from everyone but if it has a token then, more object-parameters are shown.

这对我来说是一个相对简单的配置,但我无法配置它。 我知道这不是他的工作,但为了处理 token 和资源访问,我会使用 OAUTH2 基础设施(我知道,这可以做得更好,但这是必要条件)

如下我写给你我的配置:

StartUpApplication.java

@SpringBootApplication(exclude = DispatcherServletAutoConfiguration.class)
@Import({ InMemoryDBConfigurationImpl.class})
@ImportResource({ "classpath:config/security-context.xml" })
public class SalustroApplication {

@Autowired
@Qualifier("InMemoryConfig")
private SystemConfiguration systemConfiguration;

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(SalustroApplication.class);
    app.run(args);
}

@Bean
public ServletRegistrationBean foo() {
    DispatcherServlet dispatcherServlet = new DispatcherServlet();
    AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
    applicationContext.register(FooConfig.class);
    dispatcherServlet.setApplicationContext(applicationContext);
    ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/");
    servletRegistrationBean.setName("foo");
    return servletRegistrationBean;
}

安全部分是否需要 foo 方法?

security-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2.xsd
    http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<sec:http pattern="/api/auth" create-session="stateless" 
    authentication-manager-ref="clientAuthenticationManager" use-expressions="true">
    <sec:intercept-url pattern="/api/auth" access="IS_AUTHENTICATED_FULLY" />
    <sec:anonymous enabled="false" />
    <sec:custom-filter ref="clientCredentialsTokenEndpointFilter"
        after="BASIC_AUTH_FILTER" />
    <sec:access-denied-handler ref="oauthAccessDeniedHandler" />
    <sec:http-basic entry-point-ref="clientAuthenticationEntryPoint" />
    <sec:csrf disabled="true" />
</sec:http>

<sec:http pattern="/api/**" create-session="never"
    entry-point-ref="oauthAuthenticationEntryPoint" use-expressions="true"
    access-decision-manager-ref="accessDecisionManager">
    <sec:intercept-url pattern="/api/**"
        access="isFullyAuthenticated() AND hasRole('ROLE_USER')" />
    <sec:anonymous enabled="false" />
    <sec:custom-filter ref="resourceServerFilter"
        before="PRE_AUTH_FILTER" />
    <sec:access-denied-handler ref="oauthAccessDeniedHandler" />
    <sec:csrf disabled="true" />
    <sec:headers />
</sec:http>

<sec:authentication-manager id="clientAuthenticationManager">
    <sec:authentication-provider
        user-service-ref="clientDetailsUserService" />
</sec:authentication-manager>

<bean id="accessDecisionManager"
    class="org.springframework.security.access.vote.UnanimousBased"
    c:decisionVoters-ref="votersList" />

<bean id="clientAuthenticationEntryPoint"       class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"
    p:realmName="p4me-test/client" p:typeName="Basic" />

<bean id="clientCredentialsTokenEndpointFilter"
    class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter"
    p:authenticationManager-ref="clientAuthenticationManager"
    p:filterProcessesUrl="/api/auth" />
<bean id="clientDetailsService"
    class="app.security.ClientDetailsServiceImpl" />
<bean id="clientDetailsUserService"
    class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService"
    c:clientDetailsService-ref="clientDetailsService" />

<bean id="clientDetailServiceImpl" class="app.security.ClientDetailsServiceImpl" />

<bean id="oauthAccessDeniedHandler"
    class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
<bean id="oauthAuthenticationEntryPoint"
    class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"
    p:realmName="p4me-test">
</bean>

<oauth:resource-server id="resourceServerFilter"
    resource-id="test" token-services-ref="tokenServices" />

<bean id="tokenEnhancer" class="app.security.CustomTokenEnhancer" />
<bean id="tokenServices" class="app.security.CustomTokenServices"
    p:tokenStore-ref="tokenStore" p:clientDetailsService-ref="clientDetailsService"
    p:supportRefreshToken="true" p:tokenEnhancer-ref="tokenEnhancer"
    p:accessTokenValiditySeconds="1800" />

<bean id="tokenStore"       class="org.springframework.security.oauth2.provider.token.store.InMemory TokenStore" />
    <sec:authentication-manager alias="authenticationManager">
    <sec:authentication-provider ref="userAuthenticationProvider" />
</sec:authentication-manager>
<bean id="userAuthenticationProvider"
    class="app.config.impl.security.SecureAuthenticationProvider"/>

<oauth:authorization-server
    client-details-service-ref="clientDetailsService"
    token-services-ref="tokenServices" user-approval-handler-ref="userApprovalHandler"
    token-endpoint-url="/api/auth">
    <oauth:authorization-code />
    <oauth:implicit />
    <oauth:refresh-token />
    <oauth:client-credentials />
    <oauth:password />
</oauth:authorization-server>

<bean id="userApprovalHandler"
    class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler"
    p:tokenStore-ref="tokenStore" p:requestFactory-ref="requestFactory" />

<bean id="requestFactory"
    class="org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory"
    c:clientDetailsService-ref="clientDetailServiceImpl" />

<util:list id="votersList">
    <bean class="app.security.AccessVoter" />
    <bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
    <bean class="org.springframework.security.access.vote.RoleVoter" />
    <bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
    <bean class="org.springframework.security.web.access.expression.WebExpressionVoter">
        <property name="expressionHandler">
            <bean class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />
        </property>
    </bean>
</util:list>

测试类

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SalustroApplication.class)
public class AuthTest {

@Autowired
private WebApplicationContext context;

@Autowired
private Filter springSecurityFilterChain;

@Test
public void find1() throws Exception {
    ResultActions doLogin = doLogin();
    String contentAsString = doLogin.andReturn().getResponse().getContentAsString();
    JSONObject json = new JSONObject(contentAsString);

    DefaultMockMvcBuilder webAppContextSetup = MockMvcBuilders.webAppContextSetup(context)
            .addFilter(springSecurityFilterChain);
    MockMvc build = webAppContextSetup.build();
    final ResultActions userResult = build.perform(post("/api/secure/user/1")
            .param("access_token", json.getString("access_token")).accept(MediaType.APPLICATION_JSON))
            .andDo(print());

    assertEquals(someUser, userResult);


}

protected ResultActions doLogin() throws Exception {
    DefaultMockMvcBuilder webAppContextSetup = MockMvcBuilders.webAppContextSetup(context)
            .addFilter(springSecurityFilterChain);
    MockMvc build = webAppContextSetup.build();

    final ResultActions loginResult = build.perform(post("/api/auth").param("grant_type", "password")
            .param("client_id", "testId").param("client_secret", "testSecret").param("username", "someUser")
            .param("password", "somePassword").param("scope", "read").accept(MediaType.APPLICATION_JSON)).andDo(print());

    return loginResult;
}

SecureAuthenticationPriver.class

@Component
public class SecureAuthenticationProvider implements AuthenticationProvider {

protected final static Logger logger = LoggerFactory.getLogger(SecureAuthenticationProvider.class);

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    String name = authentication.getName();
    String password = authentication.getCredentials().toString();
    List<GrantedAuthority> grantedAuths = new ArrayList<>();

    GrantedAuthority authorithy = new SimpleGrantedAuthority("USER");
    grantedAuths.add(authorithy);

    UserEntity authenticatedUser = userPersistence.findByUserName(name, password);
    if (authenticatedUser != null) {
        return new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
    } else
        return null;    }

@Override
public boolean supports(Class<?> authentication) {
    return false;
}
}

AccessVoter.class

@Service
public class AccessVoter implements AccessDecisionVoter<Object> {
@Override
public boolean supports(final ConfigAttribute attribute) {
    return true;
}

@Override
public boolean supports(final Class<?> clazz) {
    return true;
}

@Override
@Transactional
public int vote(final Authentication authentication, final Object object,
        final Collection<ConfigAttribute> attributes) {
    final Object principal = authentication.getPrincipal();
    return 1;
}

private int refreshUserDetails(final Principal principal) {
    return 1;
}
}

ClientDetailServiceImpl.class

public class ClientDetailsServiceImpl implements ClientDetailsService {

@Override
public ClientDetails loadClientByClientId(final String clientId) {
    if ("invalid".equals(clientId)) {
        throw new ClientRegistrationException(clientId + " not found");
    }
    return createClientDetails(clientId);
}

private ClientDetails createClientDetails(final String clientId) {
    final Set<GrantedAuthority> grantAuthorities = new HashSet<GrantedAuthority>();
    grantAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));

    final Set<String> authorizedGrantTypes = new HashSet<String>();
    authorizedGrantTypes.add("password");
    final BaseClientDetails details = new BaseClientDetails();
    details.setClientId("testId");
    details.setClientSecret("testSecret");
    details.setAuthorizedGrantTypes(authorizedGrantTypes);
    details.setAuthorities(grantAuthorities);
    return details;
}
}

CustomTokenEnhancer.class

@Component
public class CustomTokenEnhancer implements TokenEnhancer {

private List<TokenEnhancer> delegates = Collections.emptyList();

@Autowired
private UserService userService;

public void setTokenEnhancers(final List<TokenEnhancer> delegates) {
    this.delegates = delegates;
}

@Override
public OAuth2AccessToken enhance(final OAuth2AccessToken accessToken, final OAuth2Authentication authentication) {
    final DefaultOAuth2AccessToken tempResult = (DefaultOAuth2AccessToken) accessToken;
    // tempResult.setAdditionalInformation(getAuthenticationMethod(authentication));
    OAuth2AccessToken result = tempResult;
    for (final TokenEnhancer enhancer : delegates) {
        result = enhancer.enhance(result, authentication);
    }
    return result;
}

private boolean isAdmin(final Collection<GrantedAuthority> authorities) {
    for (final GrantedAuthority grantedAuthority : authorities) {
        if (grantedAuthority.getAuthority().compareTo("ROLE_ADMIN") == 0) {
            return true;
        }
    }
    return false;
}
}

CustomTokenServices.class

public class CustomTokenServices extends DefaultTokenServices {

private TokenStore tokenStore;

private ClientDetailsService clientDetailsService;

private TokenEnhancer accessTokenEnhancer;

@Override
public void afterPropertiesSet() throws Exception {
    Assert.notNull(tokenStore, "tokenStore must be set");
}

@Override
public OAuth2AccessToken createAccessToken(final OAuth2Authentication authentication) {

    final OAuth2AccessToken existingAccessToken = tokenStore
            .getAccessToken(authentication);
    OAuth2RefreshToken refreshToken = null;
    if (existingAccessToken != null && existingAccessToken.isExpired()) {
        if (existingAccessToken.getRefreshToken() != null) {
            refreshToken = existingAccessToken.getRefreshToken();
            tokenStore.removeRefreshToken(refreshToken);
        }
        tokenStore.removeAccessToken(existingAccessToken);
    }

    refreshToken = createRefreshToken(authentication);
    final ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
    if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
        refreshToken = createRefreshToken(authentication);
    }

    final OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
    tokenStore.storeAccessToken(accessToken, authentication);
    refreshToken = accessToken.getRefreshToken();
    if (refreshToken != null) {
        tokenStore.storeRefreshToken(refreshToken, authentication);
    }
    return accessToken;

}

@Override
public OAuth2Authentication loadAuthentication(final String accessTokenValue) {
    final DefaultOAuth2AccessToken accessToken = (DefaultOAuth2AccessToken) tokenStore
            .readAccessToken(accessTokenValue);
    if (accessToken == null) {
        throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
    } else if (accessToken.isExpired()) {
        tokenStore.removeAccessToken(accessToken);
        throw new InvalidTokenException("Access token expired: " + accessTokenValue);
    }

    final OAuth2Authentication result = tokenStore
            .readAuthentication(accessToken);

    if (clientDetailsService != null) {
        final String clientId = result.getOAuth2Request().getClientId();
        try {
            clientDetailsService.loadClientByClientId(clientId);
        } catch (final ClientRegistrationException e) {
            throw new InvalidTokenException("Client not valid: " + clientId, e);
        }
    }
    final int validitySeconds = getAccessTokenValiditySeconds(result
            .getOAuth2Request());
    accessToken
            .setExpiration(new Date(System.currentTimeMillis() + validitySeconds * 1000L));
    return result;
}

private ExpiringOAuth2RefreshToken createRefreshToken(final OAuth2Authentication authentication) {
    if (!isSupportRefreshToken(authentication.getOAuth2Request())) {
        return null;
    }
    final int validitySeconds = getRefreshTokenValiditySeconds(authentication
            .getOAuth2Request());
    final ExpiringOAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken(UUID
            .randomUUID().toString(), new Date(System.currentTimeMillis() + validitySeconds * 1000L));
    return refreshToken;
}

private OAuth2AccessToken createAccessToken(final OAuth2Authentication authentication, final OAuth2RefreshToken refreshToken) {
    final DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID
            .randomUUID().toString());
    final int validitySeconds = getAccessTokenValiditySeconds(authentication
            .getOAuth2Request());
    if (validitySeconds > 0) {
        token.setExpiration(new Date(System.currentTimeMillis() + validitySeconds * 1000L));
    }
    token.setRefreshToken(refreshToken);
    token.setScope(authentication.getOAuth2Request().getScope());

    return accessTokenEnhancer != null ? accessTokenEnhancer
            .enhance(token, authentication) : token;
}

@Override
public void setTokenEnhancer(final TokenEnhancer accessTokenEnhancer) {
    this.accessTokenEnhancer = accessTokenEnhancer;
}

@Override
public void setTokenStore(final TokenStore tokenStore) {
    this.tokenStore = tokenStore;
}

@Override
public void setClientDetailsService(final ClientDetailsService clientDetailsService) {
    this.clientDetailsService = clientDetailsService;
}

}

我一开始是怎么说的:我是从另一个工作副本的配置开始,转化到这个应用的需求。 也有可能我混淆了一些配置。

我在最后重复一遍,我将利用 OAUTH2 系统生成 token 并利用该 token 对用户进行身份验证。此身份验证是在/api/auth(或/api/secure/auth?)下进行的,资源在/api/secure下可用> 仅适用于拥有有效 token 的用户,其他资源在 /api/yyy 下可用,如果他们拥有 token ,则返回更多信息

当我运行测试以尝试获取资源时,我收到此错误:

 Body = {"error":"access_denied","error_description":"Access is denied"}

现在我不知道我必须在哪里操作。在 security-context.xml 中或添加一些类来检查 token 。

最佳答案

异常

Body = {"error":"unauthorized","error_description":"There is no client authentication. Try adding an appropriate authentication filter."}

实际上与 AuthTest 测试类相关,其中 WebApplicationContext 不包含 Spring Security 的过滤器链。您需要对 AuthTest 测试类进行以下更改。

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SalustroApplication.class)
public class AuthTest {

    @Autowired
    private WebApplicationContext context;

    // Inject this
    @Autowired
    private Filter springSecurityFilterChain;

    @Test
    public void testLogin() throws Exception {
        ResultActions doLogin = doLogin();
        assertEquals(doLogin.andReturn().getResponse().getContentAsString(), "A valid Token");
    }

    protected ResultActions doLogin() throws Exception {
        DefaultMockMvcBuilder webAppContextSetup = MockMvcBuilders.webAppContextSetup(context).addFilter(springSecurityFilterChain); // Add filter
        MockMvc build = webAppContextSetup.build();

        final ResultActions loginResult = build.perform(post("/api/auth").param("grant_type", "password")
                .param("client_id", "testId").param("client_secret", "testSecret").param("username", "aUserName")
                .param("password", "123456").accept(MediaType.APPLICATION_JSON)).andDo(print());

        return loginResult;

    }
}

关于java - Spring Security + spring oauth2解析,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34014159/

相关文章:

Java运算符,解释

java - 在java中创建帐户类并输出

java - for循环优化

java - 是否有一种通用方法来验证(基于 xml 的)spring 配置是否有效?

java - 如何以编程方式获取 HttpSamplerProxy 元素(例如 HeaderManager 及其添加的 Header)

java - 在 Java Web 服务中支持基于 IP 的白名单

security - 受密码保护的 ZIP 文件是否存在任何已知标准或安全漏洞?

java - 当我尝试读取通过 Intent 传递的 Vector 时出现 NullPointerException

spring - Spring Boot JAX-RS/CXF依赖项注入(inject)可在JAR中工作,但不能在WAR中工作

php - PHP 中的输入过滤