如何在Spring Boot应用程序上启用Bearer身份验证?

我想要实现的是:

  • 通过jdbc访问的数据库(即MySQL)中存储的用户,权限,客户端和访问令牌
  • API公开端点,让您询问“我可以拥有OAuth2承载令牌吗?我知道客户端ID和机密”
  • 如果在请求标头中提供了Bearer令牌,则API可让您访问MVC端点

我对此很了解-前两点都在起作用。

我无法为我的Spring Boot" title="Spring Boot">Spring Boot应用程序使用完全默认的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"

}

这意味着(在这种情况下)它已经退回到 匿名 身份验证。如果我添加到HttpSecurity中,则可以看到 真正的

错误.anonymous().disable()

{

"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]

两种方式:不好。

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

我想知道如何获得这样的过滤器?


这就是我的应用程序的样子。它是Kotlin,但希望对Java而言应该有意义。

@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)

}

@RestController

class TestController {

@RequestMapping("/test")

fun Test(): String {

return "hey there"

}

}

@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()

}

}

/**

* 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

}

}

@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 = ?")

}

}


有任何想法吗?

我在启动时确实收到了此类消息……这些可能与问题有关吗?

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。我添加了以下课程:

@Configuration

@EnableResourceServer

class MyResourceServerConfigurerAdapter : ResourceServerConfigurerAdapter()

并且我在调试器中确认这导致ResourceServerSecurityConfigurer进入其configure(http:

HttpSecurity)方法,看起来 确实

像它试图将a安装OAuth2AuthenticationProcessingFilter到过滤器链中。

但这看起来并不成功。根据Spring

Security的调试输出:我的过滤器链中仍然有相同数量的过滤器。OAuth2AuthenticationProcessingFilter不在那里。这是怎么回事?


:我不知道这个问题是我有 2

类(WebSecurityConfigurerAdapterResourceServerConfigurerAdapter)尝试配置HttpSecurity。它是互斥的吗?

回答:

是! 问题与我 同时 注册 a WebSecurityConfigurerAdapter

a有关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

}

}


:为了使Bearer身份验证适用于 所有 端点(例如,/metrics由Spring

Actuator安装的端点),我发现我还必须添加security.oauth2.resource.filter-order:

3application.yml。看到这个答案。

以上是 如何在Spring Boot应用程序上启用Bearer身份验证? 的全部内容, 来源链接: utcz.com/qa/404495.html

回到顶部