Spring Stateless State Security第3部分:JWT +社会认证

我的Stateless Spring Security系列文章的第三部分也是最后一部分是关于将基于JWT令牌的身份验证与spring-social-security混合在一起的。 这篇文章直接建立在此基础上,并且主要集中在已更改的部分上。 想法是使用基于OAuth 2的“使用Facebook登录”功能来替换基于用户名/密码的登录,但是此后仍使用相同的基于令牌的身份验证。

登录流程

客户端

用户单击“使用Facebook登录”按钮,该按钮是指向“ / auth / facebook”的简单链接,SocialAuthenticationFilter注意到缺少其他查询参数,并触发了将您的网站用户重定向到Facebook的重定向。 他们使用用户名/密码登录,然后重定向回“ / auth / facebook”,但这一次指定了“?code =…&state =…”参数。 (如果用户以前登录过facebook并设置了cookie,那么facebook甚至会立即重定向回该用户,并且根本不会向用户显示任何facebook屏幕。)有趣的是,您可以按照浏览器网络日志中的说明进行操作。所有操作均使用纯HTTP 302重定向完成。 (HTTP响应中的“ Location”标头用于告诉浏览器下一步要去哪里)

服务器端

从facebook重定向到“ / auth / facebook?code =…&state =…”之后,SocialAuthenticationFilter现在可以看到适当的参数,并将触发两个服务器调用Facebook。 第一个是获取已登录用户的访问令牌,第二个是通过使用访问令牌获取用户详细信息来测试整个过程是否成功。 完成所有这些操作后,就认为用户已登录,并且可以使用另一个302重定向(到“ /”)将其重定向回到应用程序的根目录。

关于Spring社交的一些话

Spring Social是用于处理社交网络的完整框架,其范围远远超出了单纯的登录方案。 除了不同的社交网络适配器之外,还有一个名为Spring Social Security的小型集成库,该库以与Spring Security更好地集成的方式实现了社交身份验证用例。 它带有一个映射到“ / auth”的SocialAuthenticationFilter,这就是我们将要使用的。

因此,设置社交身份验证需要使用简洁的Spring Social Security库配置Spring Social本身以及Spring Security

Spring社交

配置它基本上涉及扩展SocialConfigurerAdapter。 首先,您告诉它要支持哪些社交网络:

将facebook添加为提供者

@Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {cfConfig.addConnectionFactory(new FacebookConnectionFactory(env.getProperty("facebook.appKey"),env.getProperty("facebook.appSecret")));
}

它还需要知道如何获取当前用户的用户ID:

检索UserId

@Override
public UserIdSource getUserIdSource() {//retrieve the UserId from the UserAuthentication in security contextreturn new UserAuthenticationUserIdSource();
}

最后,它需要一个UsersConnectionRepository。 基本上负责用户及其与社交网络的连接之间的关系。 Spring Social带有自己的两个实现(jdbc或内存中)。 我选择自己动手,因为我想重用基于Spring Data JPA的UserDetailsS​​ervice。

自定义UsersConnectionRepository

@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {SimpleUsersConnectionRepository usersConnectionRepository =new SimpleUsersConnectionRepository(userService, connectionFactoryLocator);// if no local user record exists yet for a facebook's user id// automatically create a User and add it to the databaseusersConnectionRepository.setConnectionSignUp(autoSignUpHandler);return usersConnectionRepository;
}

Spring安全

如上一篇博客文章所述,配置它基本上涉及扩展WebSecurityConfigurerAdapter。 除了配置和公开AuthenticationManager和UserDetailsS​​ervice之类的常规内容外,它现在还需要配置和插入SocialAuthenticationFilter。 由于SpringSocialConfigurer完成了大部分工作,因此这基本上只涉及很少的代码。 它可能很简单:

@Override
protected void configure(HttpSecurity http) throws Exception {// apply the configuration from the socialConfigurer // (adds the SocialAuthenticationFilter)http.apply(new SpringSocialConfigurer());
}

考虑到我想插入基于令牌的身份验证,我自己的succesHandler和userIdSource; 我必须进行一些配置更改:

@Autowired private SocialAuthenticationSuccessHandler successHandler;
@Autowired private StatelessAuthenticationFilter jwtFilter;
@Autowired private UserIdSource userIdSource;@Override
protected void configure(HttpSecurity http) throws Exception {// Set a custom successHandler on the SocialAuthenticationFilter (saf)
final SpringSocialConfigurer sc = new SpringSocialConfigurer();
sc.addObjectPostProcessor(new ObjectPostProcessor<...>() {@Overridepublic <...> O postProcess(O saf) {saf.setAuthenticationSuccessHandler(successHandler);return saf;}
});http....// add custom authentication filter for stateless JWT based authentication
.addFilterBefore(jwtFilter, AbstractPreAuthenticatedProcessingFilter.class)// apply the configuration from the SocialConfigurer
.apply(sc.userIdSource(userIdSource));
}

如果您愿意,还可以继承SpringSocialConfigurer的子类,并为自定义的successHandler提供更优雅的设置器…

过去的样板(在这里赞誉您)

现在是时候关注一些更有趣的地方了。

在建立与Facebook的初始成功连接后,立即触发自定义ConnectionSignUp:

@Override
@Transactional
public String execute(final Connection<?> connection) {//add new users to the db with its default rolesfinal User user = new User();final String firstName = connection.fetchUserProfile().getFirstName();user.setUsername(generateUniqueUserName(firstName));user.setProviderId(connection.getKey().getProviderId());user.setProviderUserId(connection.getKey().getProviderUserId());user.setAccessToken(connection.createData().getAccessToken());grantRoles(user);userRepository.save(user);return user.getUserId();
}

如您所见,我的版本只是将用户的连接数据保留为单个JPA对象。 故意仅支持用户与Facebook上的身份之间的一对一关系。

请注意,我最终从用户生成的实际令牌中排除了连接属性。 就像我之前排除了密码字段(该字段不再是User对象的一部分)一样:

@JsonIgnore
private String accessToken;

走这条路线确实意味着对facebook API的任何调用都需要数据库查询其他连接字段。 稍后将对此进行更多介绍。

在用户通过身份验证之后,立即触发自定义AuthenticationSuccessHandler:

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) {// Lookup the complete User object from the databasefinal User user = userService.loadUserByUsername(auth.getName());// Add UserAuthentication to the responsefinal UserAuthentication ua = new UserAuthentication(user);tokenAuthenticationService.addAuthentication(response, ua);super.onAuthenticationSuccess(request, response, auth);
}

这看起来很像以前的博客文章中的代码,但是我必须在TokenAuthenticationService中进行一些更改。 由于客户端是在重定向之后加载的,因此要在此之前在客户端保留令牌,必须将其作为cookie发送给客户端:

public void addAuthentication(HttpServletResponse response, UserAuthentication authentication) {final User user = authentication.getDetails();user.setExpires(System.currentTimeMillis() + TEN_DAYS);final String token = tokenHandler.createTokenForUser(user);// Put the token into a cookie because the client can't capture response// headers of redirects / full page reloads. // (this response triggers a redirect back to "/")response.addCookie(createCookieForToken(token));
}

最终成为最终重定向响应的一部分,如下所示:

成功登录后,最终重定向回客户端

成功登录后,最终重定向回客户端

成功登录后,最终重定向回客户端

最后也是最好的部分是所有代码结合在一起形成一个非常漂亮的API。 由于Spring Social已经负责创建用户特定的请求范围的ConnectionRepository,因此可以通过将以下bean代码添加到SocialConfigurerAdapter来创建其特定于连接的API:

@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
public Facebook facebook(ConnectionRepository repo) {
Connection<Facebook> connection = repo.findPrimaryConnection(Facebook.class);return connection != null ? connection.getApi() : null;
}

此用户特定的facebook bean可以在控制器中使用,如下所示:

@Autowired
Facebook facebook;@RequestMapping(value = "/api/facebook/details", method = RequestMethod.GET)
public FacebookProfile getSocialDetails() {return facebook.userOperations().getUserProfile();
}

客户端实施

如前所述,令牌现在作为Cookie传递给客户端。 但是,就像以前一样,服务器端仍然只接受发送到特殊HTTP标头中的令牌。 承认这是相当随意的,您可以让它简单地接受cookie。 我不希望这样做,因为它可以防止CSRF攻击。 (因为无法指示浏览器将正确的身份验证令牌自动添加到请求中。)

因此,在检索当前用户详细信息之前,前端的init方法现在首先尝试将cookie移至本地存储:

$scope.init = function () {var authCookie = $cookies['AUTH-TOKEN'];if (authCookie) {TokenStorage.store(authCookie);delete $cookies['AUTH-TOKEN'];}$http.get('/api/user/current').success(function (user) {if (user.username) {$rootScope.authenticated = true;$scope.username = user.username;// For display purposes only$scope.token = JSON.parse(atob(TokenStorage.retrieve().split('.')[0]));}});
};

自定义HTTP标头的放置在与上次相同的http拦截器中进行处理。

实际的“使用Facebook登录”按钮只是触发整个重定向狂潮的链接:

<a href="/auth/facebook"><button>Login with Facebook</button></a>

为了检查实际的Facebook API是否有效,我添加了另一个按钮,用于在登录后显示来自facebook的用户详细信息。

最后的话(建议)

将我的自定义版本的JWT与社交身份验证集成在一起是一个很大的旅程。 有些部分不那么琐碎。 就像在将数据库调用卸载到JWT令牌之间找到一个很好的平衡。 最终,我选择不与客户端共享Facebook的访问令牌,因为只有在使用Facebook的API时才需要它。 这意味着对Facebook的任何查询都需要数据库调用来获取令牌。 实际上,这意味着对具有@Autowired Facebook服务的任何控制器的任何REST API调用都会导致获取请求令牌的过程非常热烈,这是请求范围的Bean创建的一部分。 但是,通过使用专用控制器进行Facebook调用可以轻松缓解这种情况,但这绝对是需要注意的。

如果您打算实际使用此代码并进行Facebook API调用,请确保您的JWT令牌在facebook令牌之前过期(当前有效期为60天)。 最好在检测到故障时实施强制重新登录,因为任何重新登录都会自动将新获取的facebook令牌存储在数据库中。

您可以在github上找到完整的工作示例。 也可以在此处找到有关如何运行它的详细信息。 我已经包含了Maven和Gradle构建文件。

翻译自: https://www.javacodegeeks.com/2015/01/stateless-spring-security-part-3-jwt-social-authentication.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/360905.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nyoj239 月老的难题 二分图 匈牙利算法

月老的难题 时间限制&#xff1a;1000 ms | 内存限制&#xff1a;65535 KB难度&#xff1a;4描述月老准备给n个女孩与n个男孩牵红线&#xff0c;成就一对对美好的姻缘。 现在&#xff0c;由于一些原因&#xff0c;部分男孩与女孩可能结成幸福的一家&#xff0c;部分可能不会结…

Web应用程序体系结构– Spring MVC – AngularJs堆栈

Spring MVC和AngularJs共同为构建表单密集型Web应用程序提供了一个真正高效且吸引人的前端开发堆栈。在这篇博客文章中&#xff0c;我们将看到如何使用这些技术构建表单密集型Web应用程序&#xff0c;并将这种方法与其他方法进行比较可用选项。 可以在此github 存储库中找到功能…

antd Datepicker组件报错 ——date.clone is not a function或者date1.isAfter is not a function

问题描述&#xff1a; antd Datepicker组件报错 ——date.clone is not a function或者date1.isAfter is not a function 原因分析&#xff1a; 在From中渲染默认值&#xff0c;一般数据请求拿到返回值存在异步&#xff0c;会晚于渲染&#xff0c;因此日期转换不能放在DatePi…

集成CDI和WebSockets

考虑尝试一个简单的Java EE 7原型应用程序&#xff0c;该应用程序涉及JAX-RS&#xff08;REST&#xff09;&#xff0c;WebSockets和CDI。 注意 &#xff1a;不想让它成为破坏者-但这篇文章主要讨论了我在尝试使用Web套接字和使用CDI作为“胶水”的REST&#xff08;在Java EE应…

Java中连接字符串的最佳方法

最近有人问我这个问题–在Java中使用运算符连接字符串是否对性能不利&#xff1f; 这让我开始思考Java中连接字符串的不同方法&#xff0c;以及它们如何相互对抗。 这些是我要研究的方法&#xff1a; 使用运算符 使用StringBuilder 使用StringBuffer 使用String.concat() …

十大最常见的Java性能问题

Java性能是所有Java应用程序开发人员都关心的问题&#xff0c;因为快速使应用程序与使其正常运行同等重要。 史蒂文海恩斯&#xff08;Steven Haines&#xff09;使用他在Java性能问题上的个人经验得出的结论是&#xff0c; 大多数问题都有共同的根本原因 。 因此&#xff0c;作…

Unity3D 访问Access数据库

Unity3D 访问Access数据库 在开始这个小教程之前呢&#xff0c;其实在网上你已经可以找到相关的资料了&#xff0c;但是我还是要把我自己做练习的一点东西分享出来。写这个教程的主要原因呢&#xff0c;是一个朋友在u3d的官网论坛里&#xff0c;找到了这个demo&#xff0c;但是…

LaTeX 基础笔记。开篇

LaTeX 的起源非常牛逼&#xff0c;有一套书大家可能听说过《计算机程序设计艺术》&#xff0c;写了好几本。当然能在计算机方面写上艺术俩字的书恐怕不是我们一般人能读懂得东西了。他的作者在1976年准备写第二卷的时候发现计算机的排版非常难看&#xff0c;所以&#xff0c;为…

Java旧版不断发展

我最近偶然发现了JDK API的一个非常有趣的警告&#xff0c;即Class.getConstructors()方法。 它的方法签名是这样的&#xff1a; Constructor<?>[] getConstructors()有趣的是&#xff0c; Class.getConstructor(Class...)返回一个Constructor<T> &#xff0c;并…

带Lambda表达式的Apache Wicket

这是怎么回事&#xff1f; :) 我一直在从事一些项目&#xff0c;这些项目值得庆幸的是将Apache Wicket用于表示层。 我自然想到Java的8个lambda表达式如何与Wicket完美匹配。 而不仅仅是我&#xff0c; Wicket团队似乎已经在努力更改API&#xff0c;以为开箱即用的lambda提供支…

装饰者模式如何拯救了我的一天

在工作中&#xff0c;我正在处理庞大的Java代码库&#xff0c;该代码库是由许多不同的开发人员在15年的时间里开发的。 并不是所有的事情都由书来完成&#xff0c;但是同时我通常没有机会重构遇到的每一个奇怪之处。 尽管如此&#xff0c;仍可以每天采取提高代码质量的措施。 …

快速的骆驼和云消息传递

Apache Camel是一个流行的&#xff0c;成熟的开源集成库。 它实现了企业集成模式 &#xff0c;这是在集成分布式系统时经常出现的一组模式。 过去&#xff0c;我写过很多关于Camel的文章&#xff0c; 包括为什么我比Spring Integration更喜欢它 &#xff0c; 路由引擎 如何 工作…

三角形类1

/* 程序的版权和版本声明部分 Copyright (c)2012, 烟台大学计算机学院学生 All rightsreserved. 文件名称&#xff1a; object.cpp 作者&#xff1a;刘清远 完成日期&#xff1a; 2013年3月29日 版本号&#xff1a; v1.0 输入描述&#xff1a;无 问题描述&#xff1a;设计求三…

android 自定义xml属性

Android 自定义组件 Android 提供了非常精致的和非常强大的组件化模型&#xff0c;能够更加方便的构建UI,这些UI组件都是基于基本的layout类:View 和 ViewGroup。 部分能够用的widgets包括&#xff1a;Button&#xff0c;TextView,EditText,ListView,CheckBox&#xff0c;Radio…

LeetCode: Longest Common Prefix

string.erase没掌握好&#xff0c;悲了个剧&#xff0c;2次过 1 class Solution {2 public:3 string longestCommonPrefix(vector<string> &strs) {4 // Start typing your C/C solution below5 // DO NOT write int main() function6 s…

流式传输大数据:Storm,Spark和Samza

有许多分布式计算系统可以实时或近实时处理大数据。 本文将从对三个Apache框架的简短描述开始&#xff0c;并试图对它们之间的某些相似之处和不同之处提供一个快速的高级概述。 阿帕奇风暴 在风暴 &#xff0c;你设计要求的T opology实时计算的图&#xff0c;然后喂到集群&…

uniapp使用阿里云多色图标

下载&#xff0c;然后解压 输入cmd&#xff0c;然后enter 输入 npm install -g iconfont-tools 再输入 iconfont-tools&#xff0c;然后一直enter&#xff0c;直到结束 目录会多了个iconfont-weapp文件&#xff0c;点击去找到 iconfont-weapp-icon.css 导入和使用 t-icon开头 接…

针对Java中的XSD验证XML

有许多工具可用于根据XSD 验证XML文档 。 其中包括操作系统脚本和工具&#xff0c;例如xmllint &#xff0c;XML编辑器和IDE&#xff0c;甚至是在线验证器。 由于前面提到的方法的局限性或问题&#xff0c;我发现拥有自己的易于使用的XML验证工具很有用。 Java使编写这样的工具…

uniapp uni.request GET方式请求,不能直接传数组解决方法

这里写目录标题目录遇到的问题 GET请求方法传数组解决方案目录 遇到的问题 GET请求方法传数组 想传一个数组&#xff0c;但是后台接受到的数据与浏览器中显示的数据和前台代码传的不一样&#xff1b; 前台代码打印 浏览器显示数据 其中HerbalNameList &#xff0c;变成了字…

休眠CascadeType.LOCK陷阱

介绍 引入了Hibernate 显式锁定支持以及Cascade Types之后 &#xff0c;就该分析CascadeType.LOCK行为了。 休眠锁定请求触发内部LockEvent 。 关联的DefaultLockEventListener可以将锁定请求级联到锁定实体子级。 由于CascadeType.ALL也包括CascadeType.LOCK &#xff0c;因…