首页 文章

如何在grails和现有的oauth2提供程序中使用spring security实现基于表单的登录

提问于
浏览
0

大家好我有一个新手grails / spring安全问题 . 我们使用spring-security-oauth2-provider在grails 3项目中设置oauth2,似乎可以保护我们的REST API .

但后来我们开始使用GSP在同一个项目中添加一个Web前端,并走到了一个十字路口 . 通常,oauth2通过对 endpoints 进行身份验证并接收令牌来工作,使用该令牌可以在后续请求中的HTTP头中使用该令牌以继续访问受保护资源 . 但我的Web前端有一个登录页面 . 所以最初我们认为将Web前端视为客户端之一(我们的iOS应用程序有1个客户端,Android应用程序有1个客户端,所以为什么我们的Web应用程序也没有1个) . 但是从我们的控制器代码(用于登录)向我们的oauth2提供者 endpoints 发出HTTP请求似乎很奇怪,因为它在同一个项目中;我的Web前端需要进行的大多数后续请求,我们希望直接访问底层服务和域对象,因此添加额外的跃点似乎适得其反 .

所以我们选择的是,当我使用我的login.gsp登录时,在控制器代码中,我绕过oauth2登录,并使用authenticationManager.authenticate()和我使用用户名构造的UsernamePasswordAuthenticationToken进行直接 spring 安全认证和密码字段传递到我的表单,然后在响应上调用SecurityContextHolder.getContext() . setAuthentication() . 这种方法解决了我的问题,因为我从this帖子那里得知,这样做只会在当前线程上设置SecurityContextHolder,并且由于SecurityContextHolder在过滤器链的末尾被清除,后续请求将不会被验证 . 所以我所做的就是如果这个身份验证通过(即没有抛出异常),那么我将我的用户对象放在HTTP会话中,并在所有后续请求中,尝试将其作为我认证的"telling"的方式检索 . 但这看起来既黑又脏;它导致我的用户对象没有附加到数据库会话(导致延迟初始化异常) .

我确实从this这样的帖子中找到了其他类似的建议,它将整个SecurityContext放在HTTP会话中,但似乎没有说明如何在后续请求中使用该SecurityContext .

我想我的最终问题是,我们是否采取了错误的方式?是否有更好,更清洁的方式来完成我想做的事情?我想我们不能成为第一个尝试这样做的人 .

2 回答

  • 0

    有点难过,我正在回答我自己的问题 . 但我想知道我的解决方案是否是解决此问题的正确方法 .

    所以我们最终做的是使用过滤器 . 我们知道拦截器应该是Grails 3的方法,但在到达之前我们只想拥有一个带过滤器的工作版本 .

    在我们的AuthController.signIn方法中,我们通过UsernamePasswordAuthenticationToken身份验证过程并实际将SecurityContext推送到HTTP会话中 . 顺便说一句,有趣的是,我后来发现这实际上已经为我做了(使用“SPRING_SECURITY_CONTEXT”键),不需要我显式地执行session.setAttribute . 我的理由是,由于SecurityContext仅使用UsernamePasswordAuthenticationToken对此请求进行了身份验证,因此我需要将其保留在HTTP会话中,以便我可以为后续请求重新加载它 . 因此,在我作为第一个顺序放置的过滤器中,我检查HTTP会话中的SecurityContext并将其“身份验证”复制到当前的SecurityContext中 . 请注意,如果在HTTP会话中找不到任何内容,我们还有一个顺序放置在最后的过滤器,用于将用户重定向到登录页面 .

    通过此更改,我们现在可以访问Controller中的springSecurityService来访问当前登录的用户(在我们无法访问之前) . 我们得到的其他细节包括能够在我们的GSP页面中使用sec:ifAllGranted . 但有些事情仍然不可行 - 我们似乎仍然无法使用@Secured(“#oauth2.isUser()”)注释来保护我们的Controller方法,总是点击401.也许我们使用UsernamePasswordAuthenticationToken进行身份验证仍然不是我们需要“完整”认证吗?

    我通过将OAuth2访问令牌而不是SecurityContext推入HTTP Session进一步尝试,并在我的过滤器中添加了“Authorization”HTTP头具有“Bearer”accessToken的值 . 这不起作用,初步记录显示在一个HTTP请求(页面视图)中,我多次进入我的过滤器,并且只有第一次成功将OAuth2访问令牌放入请求的HTTP头中 .

    所以在一天结束时,我想知道的是,我的方法是否正确,使用过滤器来保持用户在会话中连接?或者我应该采用不同的方式做到这一点?另外,如何使用@Secured注释保护我的控制器?

  • 0

    我想知道如果我已经给出答案,SO会如何对待这个答案 . 唉,真正关心的人 - 无论如何我都是那个问这个问题的人 .

    通过我查看的所有文档,例如Grails Security - Reference DocumentationSpring Security Core Plugin - Reference DocumentationGrails Spring Security Core Plugin Custom Authentication以及How to customize spring security plugin Login page in grails我能够发现即使我们使用的是Spring Security OAuth2插件,我们也获得了Spring Security Core Plug-In提供的所有功能 . 这意味着开箱即用支持基于表单的登录,并且还支持自定义登录表单 .

    使用自定义登录表单,而不是让login.gsp调用我们自己的AuthController的signIn方法,我们仍然可以使用$ /的LoginController(随Spring Security插件一起提供)的“authenticate”方法登录/验证 .

    所以我实际上尝试了这一点,当然首先禁用我们的拦截器然后最终有一个导致浏览器保释的重定向循环 . 我认为我们需要做的一些配置(使用chainMap或过滤器)我们没有,这就是为什么它仍然不起作用 .

    有一点帮助是在grails-app / conf / logback.groovy中添加以下两行

    logger 'org.springframework.security', DEBUG, ['STDOUT'], false
    logger 'grails.plugin.springsecurity', DEBUG, ['STDOUT'], false
    

    这给了我很多日志信息以帮助调试 . 出于某种原因,我意识到它在我的请求中以某种方式推送了一个匿名令牌,因为某些内容未经过身份验证 .

    经过一段时间的调试后,我终于发现答案在于application.groovy . 我遇到的问题是,因为默认情况下所有应用程序都有一个Spring Security filterChain,其最后一行/ **匹配JOINED_FILTERS . 我们在安装Spring Security OAuth2 Provider时添加的行与/ **匹配JOINED_FILTERS减去一堆其他过滤器 . 该行实际上是正确的,但因为我们从未删除原始/ **行,并且优先顺序是在此之前 . 所以我需要做的就是删除与JOINED_FILTERS匹配的第一行/ **,现在登录工作正常 .

    最后,我们在filterChain中的行是:

    [pattern: '/rest/**', filters: 'JOINED_FILTERS,-securityContextPersistenceFilter,-logoutFilter,-authenticationProcessingFilter,-rememberMeAuthenticationFilter,-oauth2BasicAuthenticationFilter,-exceptionTranslationFilter'],
    // We want all the other resources to be Web-based
    [pattern: '/web/**',  filters: 'JOINED_FILTERS,-statelessSecurityContextPersistenceFilter,-oauth2ProviderFilter,-clientCredentialsTokenEndpointFilter,-oauth2BasicAuthenticationFilter,-oauth2ExceptionTranslationFilter'],
    // This is just a catch-all that defaults to the same logic as before, it also would match things like /oauth/** or /auth/**
    [pattern: '/**',      filters: 'JOINED_FILTERS']
    

    我们还有一些指向我们自己的AuthController的覆盖的其他配置:

    grails.plugin.springsecurity.auth.loginFormUrl = '/auth/login' // This is our own Login Form
    grails.plugin.springsecurity.failureHandler.defaultFailureUrl = '/auth/login' // Exception message shown in controller
    grails.plugin.springsecurity.adh.errorPage = '/auth/denied' // Copied from LoginController
    

相关问题