Comment permettre au Porteur d'authentification sur le Printemps de Démarrage de l'application?

Ce que je suis en train de réaliser est:

  • les utilisateurs, les autorités, les clients et les jetons d'accès stockées dans une base de données (i.e. MySQL) accessible via jdbc
  • API expose les points de terminaison pour vous demandez "puis-je avoir un OAuth2 porteur du jeton? Je sais que l'identification du client et secret"
  • API vous permet d'accéder à la MVC points de terminaison si vous fournissez un Porteur du jeton dans votre en-tête de demande

J'ai obtenu assez loin avec ce les deux premiers points sont à travailler.

Je n'étais pas en mesure d'utiliser un complètement défaut OAuth2 d'installation pour mon Ressort de Démarrage de l'application, parce que la norme de la table des noms sont déjà en usage dans ma base de données (j'ai une table "users" déjà, par exemple).

J'ai construit de mes propres instances de JdbcTokenStore, JdbcClientDetailsService, et JdbcAuthorizationCodeServices manuellement, configuré pour utiliser l'outil de noms de table de ma base de données, et de créer mon application pour l'utilisation de ces instances.


Donc, voici ce que j'ai jusqu'à présent. Je peux vous demander un Porteur du jeton:

# 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

- Je recevoir une réponse; nice!

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

Maintenant, j'essaie de utilisation ce jeton:

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

Hélas:

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

Cela signifie, dans ce cas particulier) qu'il est retombé à anonyme d'authentification. Vous pouvez voir le réel erreur si j'ajoute .anonymous().disable() à mon HttpSecurity:

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

J'ai étudié plus profondément en augmentant le niveau de détail de consignation:

logging.level:
    org.springframework:
      security: DEBUG

Ce qui révèle le 10 filtres à travers lesquels ma demande de voyages:

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]

C'est à quoi il ressemble si les utilisateurs Anonymes sont désactivé. Si ils sont activé: AnonymousAuthenticationFilter est ajoutée dans la chaîne de filtres juste après SecurityContextHolderAwareRequestFilter, et la fin de la séquence de plus comme ceci:

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]

De la manière suivante: rien de bon.

Essentiellement, il m'indique qu'il nous manque une étape dans la chaîne de filtrage. Nous avons besoin d'un filtre qui permettrait de lire l'en-tête de la ServletRequest, puis remplir le contexte de sécurité de l'authentification:

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

Je me demande comment faire pour obtenir un tel filtre?


C'est ce que mon application ressemble. C'est Kotlin, mais j'espère que ça doit faire sens pour la Java des yeux.

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

Des idées? Pourrait-il être que j'ai besoin de quelque sorte d'installer le OAuth2AuthenticationProcessingFilter dans mon filtre de la chaîne d'?

Je ne reçois ces messages de démarrage... pourrait-il être lié au problème?

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.

EDIT:

Il ressemble à l'installation de OAuth2AuthenticationProcessingFilter est le travail d'un ResourceServerConfigurerAdapter. J'ai ajouté la classe suivante:

MyResourceServerConfigurerAdapter:

@Configuration
@EnableResourceServer
class MyResourceServerConfigurerAdapter : ResourceServerConfigurerAdapter()

Et je confirme, dans le débogueur que cela provoque ResourceServerSecurityConfigurer pour entrer dans son configure(http: HttpSecurity) méthode, qui ne l'impression qu'elle essaie d'installer un OAuth2AuthenticationProcessingFilter dans la chaîne de filtres.

Mais il ne regarde pas comme il a réussi. En fonction de Printemps de Sécurité de la sortie de débogage: j'ai toujours le même nombre de filtres dans mon filtre de la chaîne. OAuth2AuthenticationProcessingFilter n'est pas là. Ce qui se passe?


EDIT2: je me demande si le problème c'est que j'ai deux classes (WebSecurityConfigurerAdapter, ResourceServerConfigurerAdapter) en essayant de configurer HttpSecurity. Est-ce mutuellement exclusifs?

OriginalL'auteur Birchlabs | 2017-07-07