如何启用春天启动应用程序承载认证?

Birchlabs:

我想实现的是:

  • 用户,部门,客户和访问令牌存储在数据库中(即MySQL的)来访问通过JDBC
  • API公开端点,你要问:“我能有一个OAuth2用户承载令牌?我知道客户端ID和秘密”
  • API可以让你在你的请求头提供一个承载令牌访问MVC端点

我得到这个很远 - 前两个点都在工作。

我无法使用完全默认的OAuth2设置为我的春节,启动应用程序,因为标准表名已经在使用我的数据库(我有一个“用户”表已经为例)。

我手动建立我自己的JdbcTokenStore,JdbcClientDetailsS​​ervice和JdbcAuthorizationCodeServices的情况下,配置他们从我的数据库使用自定义表名,并设置我的应用程序使用这些实例。


所以,这里是我到目前为止所。我可以问一个承载令牌:

# The `-u` switch provides the client ID & secret over HTTP Basic Auth 
curl -u8fc9d384-619a-11e7-9fe6-246798c61721:9397ce6c-619a-11e7-9fe6-246798c61721 \
'http://localhost:8080/oauth/token' \
-d grant_type=password \
-d username=bob \
-d password=tom

我接收的响应; 太好了!

{"access_token":"1ee9b381-e71a-4e2f-8782-54ab1ce4d140","token_type":"bearer","refresh_token":"8db897c7-03c6-4fc3-bf13-8b0296b41776","expires_in":26321,"scope":"read write"}

现在,我尝试使用该令牌:

curl 'http://localhost:8080/test' \
-H "Authorization: Bearer 1ee9b381-e71a-4e2f-8782-54ab1ce4d140"

唉:

{
   "timestamp":1499452163373,
   "status":401,
   "error":"Unauthorized",
   "message":"Full authentication is required to access this resource",
   "path":"/test"
}

这意味着(在这种特殊情况下),它已回落到匿名身份验证。你可以看到真正的错误,如果我添加.anonymous().disable()到我的HttpSecurity:

{
   "timestamp":1499452555312,
   "status":401,
   "error":"Unauthorized",
   "message":"An Authentication object was not found in the SecurityContext",
   "path":"/test"
}

我深入研究这更增加了日志记录级别:

logging.level:
    org.springframework:
      security: DEBUG

这揭示了10个过滤器,通过它我的要求旅行:

o.s.security.web.FilterChainProxy        : /test at position 1 of 10 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
o.s.security.web.FilterChainProxy        : /test at position 2 of 10 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.
o.s.security.web.FilterChainProxy        : /test at position 3 of 10 in additional filter chain; firing Filter: 'HeaderWriterFilter'
o.s.security.web.FilterChainProxy        : /test at position 4 of 10 in additional filter chain; firing Filter: 'LogoutFilter'
o.s.security.web.FilterChainProxy        : /test at position 5 of 10 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
o.s.security.web.FilterChainProxy        : /test at position 6 of 10 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
o.s.security.web.FilterChainProxy        : /test at position 7 of 10 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
o.s.security.web.FilterChainProxy        : /test at position 8 of 10 in additional filter chain; firing Filter: 'SessionManagementFilter'
o.s.security.web.FilterChainProxy        : /test at position 9 of 10 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
o.s.security.web.FilterChainProxy        : /test at position 10 of 10 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /test; Attributes: [authenticated]
o.s.s.w.a.ExceptionTranslationFilter     : Authentication exception occurred; redirecting to authentication entry point

org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:379) ~[spring-security-core-4.2.3.RELEASE.jar:4.2.3.RELEASE]

那是什么样子,如果匿名用户是禁用的如果他们AnonymousAuthenticationFilter刚后加入到过滤器链SecurityContextHolderAwareRequestFilter,而序列两端更是这样的:

o.s.security.web.FilterChainProxy        : /test at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /test; Attributes: [authenticated]
o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@5ff24abf, returned: -1
o.s.s.w.a.ExceptionTranslationFilter     : Access is denied (user is anonymous); redirecting to authentication entry point

org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-4.2.3.RELEASE.jar:4.2.3.RELEASE]

无论哪种方式:没有好。

本质上,它表明,我认为我们缺少滤镜链中的一些步骤。我们需要一个过滤器会读取ServletRequest中的标题,然后填充安全上下文中的身份验证:

SecurityContextHolder.getContext().setAuthentication(request: HttpServletRequest);

我想知道如何得到这样一个过滤器?


这是我的应用程序是什么样子。这是科特林,但希望它应该是有意义的Java眼睛。

Application.kt:

@SpringBootApplication(scanBasePackageClasses=arrayOf(
        com.example.domain.Package::class,
        com.example.service.Package::class,
        com.example.web.Package::class
))
class MyApplication

fun main(args: Array<String>) {
    SpringApplication.run(MyApplication::class.java, *args)
}

的TestController:

@RestController
class TestController {
    @RequestMapping("/test")
    fun Test(): String {
        return "hey there"
    }
}

MyWebSecurityConfigurerAdapter:

@Configuration
@EnableWebSecurity
/**
 * Based on:
 * https://stackoverflow.com/questions/25383286/spring-security-custom-userdetailsservice-and-custom-user-class
 *
 * Password encoder:
 * http://www.baeldung.com/spring-security-authentication-with-a-database
 */
class MyWebSecurityConfigurerAdapter(
        val userDetailsService: MyUserDetailsService
) : WebSecurityConfigurerAdapter() {

    private val passwordEncoder = BCryptPasswordEncoder()

    override fun userDetailsService() : UserDetailsService {
        return userDetailsService
    }

    override fun configure(auth: AuthenticationManagerBuilder) {
        auth
                .authenticationProvider(authenticationProvider())
    }

    @Bean
    fun authenticationProvider() : AuthenticationProvider {
        val authProvider = DaoAuthenticationProvider()
        authProvider.setUserDetailsService(userDetailsService())
        authProvider.setPasswordEncoder(passwordEncoder)
        return authProvider
    }

    override fun configure(http: HttpSecurity?) {
        http!!
                .anonymous().disable()
                .authenticationProvider(authenticationProvider())
                .authorizeRequests()
                    .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                .csrf().disable()
    }
}

MyAuthorizationServerConfigurerAdapter:

/**
 * Based on:
 * https://github.com/spring-projects/spring-security-oauth/blob/master/tests/annotation/jdbc/src/main/java/demo/Application.java#L68
 */
@Configuration
@EnableAuthorizationServer
class MyAuthorizationServerConfigurerAdapter(
        val auth : AuthenticationManager,
        val dataSource: DataSource,
        val userDetailsService: UserDetailsService

) : AuthorizationServerConfigurerAdapter() {

    private val passwordEncoder = BCryptPasswordEncoder()

    @Bean
    fun tokenStore(): JdbcTokenStore {
        val tokenStore = JdbcTokenStore(dataSource)
        val oauthAccessTokenTable = "auth_schema.oauth_access_token"
        val oauthRefreshTokenTable = "auth_schema.oauth_refresh_token"
        tokenStore.setDeleteAccessTokenFromRefreshTokenSql("delete from ${oauthAccessTokenTable} where refresh_token = ?")
        tokenStore.setDeleteAccessTokenSql("delete from ${oauthAccessTokenTable} where token_id = ?")
        tokenStore.setDeleteRefreshTokenSql("delete from ${oauthRefreshTokenTable} where token_id = ?")
        tokenStore.setInsertAccessTokenSql("insert into ${oauthAccessTokenTable} (token_id, token, authentication_id, " +
                "user_name, client_id, authentication, refresh_token) values (?, ?, ?, ?, ?, ?, ?)")
        tokenStore.setInsertRefreshTokenSql("insert into ${oauthRefreshTokenTable} (token_id, token, authentication) values (?, ?, ?)")
        tokenStore.setSelectAccessTokenAuthenticationSql("select token_id, authentication from ${oauthAccessTokenTable} where token_id = ?")
        tokenStore.setSelectAccessTokenFromAuthenticationSql("select token_id, token from ${oauthAccessTokenTable} where authentication_id = ?")
        tokenStore.setSelectAccessTokenSql("select token_id, token from ${oauthAccessTokenTable} where token_id = ?")
        tokenStore.setSelectAccessTokensFromClientIdSql("select token_id, token from ${oauthAccessTokenTable} where client_id = ?")
        tokenStore.setSelectAccessTokensFromUserNameAndClientIdSql("select token_id, token from ${oauthAccessTokenTable} where user_name = ? and client_id = ?")
        tokenStore.setSelectAccessTokensFromUserNameSql("select token_id, token from ${oauthAccessTokenTable} where user_name = ?")
        tokenStore.setSelectRefreshTokenAuthenticationSql("select token_id, authentication from ${oauthRefreshTokenTable} where token_id = ?")
        tokenStore.setSelectRefreshTokenSql("select token_id, token from ${oauthRefreshTokenTable} where token_id = ?")
        return tokenStore
    }

    override fun configure(security: AuthorizationServerSecurityConfigurer?) {
        security!!.passwordEncoder(passwordEncoder)
    }

    override fun configure(clients: ClientDetailsServiceConfigurer?) {
        val clientDetailsService = JdbcClientDetailsService(dataSource)
        clientDetailsService.setPasswordEncoder(passwordEncoder)

        val clientDetailsTable = "auth_schema.oauth_client_details"
        val CLIENT_FIELDS_FOR_UPDATE = "resource_ids, scope, " +
                "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, " +
                "refresh_token_validity, additional_information, autoapprove"
        val CLIENT_FIELDS = "client_secret, ${CLIENT_FIELDS_FOR_UPDATE}"
        val BASE_FIND_STATEMENT = "select client_id, ${CLIENT_FIELDS} from ${clientDetailsTable}"

        clientDetailsService.setFindClientDetailsSql("${BASE_FIND_STATEMENT} order by client_id")
        clientDetailsService.setDeleteClientDetailsSql("delete from ${clientDetailsTable} where client_id = ?")
        clientDetailsService.setInsertClientDetailsSql("insert into ${clientDetailsTable} (${CLIENT_FIELDS}," +
                " client_id) values (?,?,?,?,?,?,?,?,?,?,?)")
        clientDetailsService.setSelectClientDetailsSql("${BASE_FIND_STATEMENT} where client_id = ?")
        clientDetailsService.setUpdateClientDetailsSql("update ${clientDetailsTable} set " +
                "${CLIENT_FIELDS_FOR_UPDATE.replace(", ", "=?, ")}=? where client_id = ?")
        clientDetailsService.setUpdateClientSecretSql("update ${clientDetailsTable} set client_secret = ? where client_id = ?")
        clients!!.withClientDetails(clientDetailsService)
    }

    override fun configure(endpoints: AuthorizationServerEndpointsConfigurer?) {
        endpoints!!
                .authorizationCodeServices(authorizationCodeServices())
                .authenticationManager(auth)
                .tokenStore(tokenStore())
                .approvalStoreDisabled()
                .userDetailsService(userDetailsService)
    }

    @Bean
    protected fun authorizationCodeServices() : AuthorizationCodeServices {
        val codeServices = JdbcAuthorizationCodeServices(dataSource)
        val oauthCodeTable = "auth_schema.oauth_code"
        codeServices.setSelectAuthenticationSql("select code, authentication from ${oauthCodeTable} where code = ?")
        codeServices.setInsertAuthenticationSql("insert into ${oauthCodeTable} (code, authentication) values (?, ?)")
        codeServices.setDeleteAuthenticationSql("delete from ${oauthCodeTable} where code = ?")
        return codeServices
    }
}

MyAuthorizationServerConfigurerAdapter:

@Service
class MyUserDetailsService(
        val theDataSource: DataSource
) : JdbcUserDetailsManager() {
    @PostConstruct
    fun init() {
        dataSource = theDataSource

        val usersTable = "auth_schema.users"
        val authoritiesTable = "auth_schema.authorities"

        setChangePasswordSql("update ${usersTable} set password = ? where username = ?")
        setCreateAuthoritySql("insert into ${authoritiesTable} (username, authority) values (?,?)")
        setCreateUserSql("insert into ${usersTable} (username, password, enabled) values (?,?,?)")
        setDeleteUserAuthoritiesSql("delete from ${authoritiesTable} where username = ?")
        setDeleteUserSql("delete from ${usersTable} where username = ?")
        setUpdateUserSql("update ${usersTable} set password = ?, enabled = ? where username = ?")
        setUserExistsSql("select username from ${usersTable} where username = ?")

        setAuthoritiesByUsernameQuery("select username,authority from ${authoritiesTable} where username = ?")
        setUsersByUsernameQuery("select username,password,enabled from ${usersTable} " + "where username = ?")
    }
}

有任何想法吗?难道是因为我需要以某种方式安装OAuth2AuthenticationProcessingFilter到我的过滤器链?

我启动时获取这样的信息......这些可能涉及到这个问题?

u.c.c.h.s.auth.MyUserDetailsService      : No authentication manager set. Reauthentication of users when changing passwords will not be performed.
s.c.a.w.c.WebSecurityConfigurerAdapter$3 : No authenticationProviders and no parentAuthenticationManager defined. Returning null.

编辑:

它看起来像安装OAuth2AuthenticationProcessingFilter是的工作ResourceServerConfigurerAdapter我已经添加了下面的类:

MyResourceServerConfigurerAdapter:

@Configuration
@EnableResourceServer
class MyResourceServerConfigurerAdapter : ResourceServerConfigurerAdapter()

我在调试确认这将导致ResourceServerSecurityConfigurer进入它的configure(http: HttpSecurity)方法,它喜欢它试图安装一看OAuth2AuthenticationProcessingFilter进入过滤器链。

不过,这并不像它成功了。据Spring Security的调试输出:我仍然在我的过滤器链相同数量的过滤器。OAuth2AuthenticationProcessingFilter是不是在那里。这是怎么回事?


EDIT2:我不知道这个问题是我有2类(WebSecurityConfigurerAdapterResourceServerConfigurerAdapter)尝试配置HttpSecurity。是不是相互排斥?

Birchlabs:

是! 这个问题是关系到我已经注册的事实,WebSecurityConfigurerAdapter ResourceServerConfigurerAdapter

解决办法:删除WebSecurityConfigurerAdapter并使用此ResourceServerConfigurerAdapter

@Configuration
@EnableResourceServer
class MyResourceServerConfigurerAdapter(
        val userDetailsService: MyUserDetailsService
) : ResourceServerConfigurerAdapter() {
    private val passwordEncoder = BCryptPasswordEncoder()

    override fun configure(http: HttpSecurity?) {
        http!!
                .authenticationProvider(authenticationProvider())
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                .csrf().disable()
    }

    @Bean
    fun authenticationProvider() : AuthenticationProvider {
        val authProvider = DaoAuthenticationProvider()
        authProvider.setUserDetailsService(userDetailsService)
        authProvider.setPasswordEncoder(passwordEncoder)
        return authProvider
    }
}

编辑:为了让承载AUTH适用于所有端点(例如/metrics通过弹簧操动安装端点),我发现,我不得不也加入security.oauth2.resource.filter-order: 3到我的application.yml这个答案

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

春天启动的桌面应用程序

使用AWS Cognito的Java API认证/ autorization春天启动的Web应用程序

如何获得春天启动应用程序的jar父文件夹的路径动态?

春天启动的请求:“重新运行带有‘调试’启用应用程序” - 我该怎么办?

如何部署自定义.properties文件到AWS ElasticBeanstalk一个春天启动应用程序?

JAXB ClassNotFoundException的建筑春天启动应用程序2.2.0与Java 11

的java.net.UnknownHostException dockerized的MySQL从春天启动应用程序

春天启动的应用程序不会产生脂肪(尤伯杯)的容器

为什么不是今年春天启动的Web应用程序所需的@Repository?

春天启动的Web应用程序不能在Tomcat运行9

无法角项目,春天启动的Web应用程序连接

jvmArguments不是可执行春天启动的应用程序解释

为什么春天启动2.0应用程序不运行schema.sql文件?

杰克逊被忽略了我的春天启动的应用程序spring.jackson.properties

如何调用从春天启动应用Oracle函数?

是否有可能开始嵌入我的春天启动的应用程序的轴突服务器?

H2没有创造/在我的春天启动的应用程序更新表。什么是错的我的实体?

春天启动的应用程序失败方法org.postgresql.jdbc4.Jdbc4Connection.createClob()尚未实现

如何使用火力地堡和春天启动REST应用?

如何创建春天启动的自定义注释?

如何返回从春天启动控制器的超链接?

在同一应用程序项目上对API使用承载令牌认证,对MVC使用OpenId认证

Kubernetes:如何启用API服务器承载令牌认证?

春天引导应用程序内部是如何工作?

春天启动的microService API版本实现

在编辑春天启动的通知

春天启动不带主类

如何为 macOS 菜单栏应用程序启用自动启动?

春天启动的单元测试运行整个程序