如何在 Spring 引导应用程序上启用 Bearer 身份验证?
How to enable Bearer authentication on Spring Boot application?
我想要实现的是:
- 存储在数据库(即MySQL)中的用户、权限、客户端和访问令牌,通过jdbc
访问
- API 公开端点供您询问 "can I have an OAuth2 bearer token? I know the client ID and secret"
如果您在请求中提供 Bearer 令牌,- API 允许您访问 MVC 端点 header
我在这方面做得很好——前两点很有效。
我无法为我的 Spring 启动应用程序使用完全默认的 OAuth2 设置,因为标准 table 名称已经 in-use 在我的数据库中(我有一个 "users" table 已经,例如)。
我手动构建了自己的 JdbcTokenStore、JdbcClientDetailsService 和 JdbcAuthorizationCodeServices 实例,将它们配置为使用来自我的数据库的自定义 table 名称,并将我的应用程序设置为使用这些实例。
所以,这就是我目前所拥有的。我可以索要 Bearer 令牌:
# 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:
,您会看到 real 错误
{
"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]
如果匿名用户 被禁用 ,这就是它的样子。如果它们是 enabled: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 的 header,然后填充安全上下文的身份验证:
SecurityContextHolder.getContext().setAuthentication(request: HttpServletRequest);
我想知道如何获得这样的过滤器?
这就是我的应用程序的样子。它是 Kotlin,但希望它对 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)
}
测试控制器:
@RestController
class TestController {
@RequestMapping("/test")
fun Test(): String {
return "hey there"
}
}
MyWebSecurityConfigurerAdapter:
@Configuration
@EnableWebSecurity
/**
* Based on:
*
*
* 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 : No authenticationProviders and no parentAuthenticationManager defined. Returning null.
编辑:
看起来安装 OAuth2AuthenticationProcessingFilter
是 ResourceServerConfigurerAdapter
的工作。我添加了以下 class:
MyResourceServerConfigurerAdapter:
@Configuration
@EnableResourceServer
class MyResourceServerConfigurerAdapter : ResourceServerConfigurerAdapter()
并且我在调试器中确认这会导致 ResourceServerSecurityConfigurer
进入它的 configure(http: HttpSecurity)
方法,确实 看起来它试图安装一个 OAuth2AuthenticationProcessingFilter
进入过滤器链。
不过好像没有成功。根据 Spring Security 的调试输出:我的过滤器链中仍然有相同数量的过滤器。 OAuth2AuthenticationProcessingFilter
不在里面。怎么回事?
EDIT2:我想知道问题是我有两个 classes (WebSecurityConfigurerAdapter
, ResourceServerConfigurerAdapter
) 正在尝试配置 HttpSecurity。是互斥的吗?
是的!这个问题与我注册了 both a WebSecurityConfigurerAdapter
and a ResourceServerConfigurerAdapter
.[=19= 有关]
解决方法:删除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 auth 应用于 all 端点(例如 /metrics
安装的端点Spring 执行器),我发现我还必须将 security.oauth2.resource.filter-order: 3
添加到我的 application.yml
。参见 。
我想要实现的是:
- 存储在数据库(即MySQL)中的用户、权限、客户端和访问令牌,通过jdbc 访问
- API 公开端点供您询问 "can I have an OAuth2 bearer token? I know the client ID and secret" 如果您在请求中提供 Bearer 令牌,
- API 允许您访问 MVC 端点 header
我在这方面做得很好——前两点很有效。
我无法为我的 Spring 启动应用程序使用完全默认的 OAuth2 设置,因为标准 table 名称已经 in-use 在我的数据库中(我有一个 "users" table 已经,例如)。
我手动构建了自己的 JdbcTokenStore、JdbcClientDetailsService 和 JdbcAuthorizationCodeServices 实例,将它们配置为使用来自我的数据库的自定义 table 名称,并将我的应用程序设置为使用这些实例。
所以,这就是我目前所拥有的。我可以索要 Bearer 令牌:
# 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]
如果匿名用户 被禁用 ,这就是它的样子。如果它们是 enabled: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 的 header,然后填充安全上下文的身份验证:
SecurityContextHolder.getContext().setAuthentication(request: HttpServletRequest);
我想知道如何获得这样的过滤器?
这就是我的应用程序的样子。它是 Kotlin,但希望它对 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)
}
测试控制器:
@RestController
class TestController {
@RequestMapping("/test")
fun Test(): String {
return "hey there"
}
}
MyWebSecurityConfigurerAdapter:
@Configuration
@EnableWebSecurity
/**
* Based on:
*
*
* 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 : No authenticationProviders and no parentAuthenticationManager defined. Returning null.
编辑:
看起来安装 OAuth2AuthenticationProcessingFilter
是 ResourceServerConfigurerAdapter
的工作。我添加了以下 class:
MyResourceServerConfigurerAdapter:
@Configuration
@EnableResourceServer
class MyResourceServerConfigurerAdapter : ResourceServerConfigurerAdapter()
并且我在调试器中确认这会导致 ResourceServerSecurityConfigurer
进入它的 configure(http: HttpSecurity)
方法,确实 看起来它试图安装一个 OAuth2AuthenticationProcessingFilter
进入过滤器链。
不过好像没有成功。根据 Spring Security 的调试输出:我的过滤器链中仍然有相同数量的过滤器。 OAuth2AuthenticationProcessingFilter
不在里面。怎么回事?
EDIT2:我想知道问题是我有两个 classes (WebSecurityConfigurerAdapter
, ResourceServerConfigurerAdapter
) 正在尝试配置 HttpSecurity。是互斥的吗?
是的!这个问题与我注册了 both a WebSecurityConfigurerAdapter
and a ResourceServerConfigurerAdapter
.[=19= 有关]
解决方法:删除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 auth 应用于 all 端点(例如 /metrics
安装的端点Spring 执行器),我发现我还必须将 security.oauth2.resource.filter-order: 3
添加到我的 application.yml
。参见