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

stateless

我的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

stateless

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

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

相关文章

return在c语言中是什么意思

函数是C语言的基本构件&#xff0c;一个C程序可以由一个主函数和若干个子程序函数构成&#xff0c;由主函数调用其它子程序函数&#xff0c;其他子程序函数也可以互相调用。通常希望通过函数调用使主函数能得到一个确定的值&#xff0c;这就是函数的返回值。在C语言中通过函数实…

为什么非阻塞io性能更好_提高性能:流的非阻塞处理

为什么非阻塞io性能更好1.简介 想象一下&#xff0c;我们有一个需要访问外部Web服务的应用程序&#xff0c;以便收集有关客户端的信息&#xff0c;然后对其进行处理。 更具体地说&#xff0c;我们无法在一次调用中获得所有这些信息。 如果我们要查找不同的客户端&#xff0c;则…

linux history文件路径,Linux、Unix常用命令(文件和目录相关)

mkdir dirname 建立子目录. 注意:用户不能在一个不存在的目录中建立子目录。mkdir data 在当前目录下建立子目录 datamkdir /usr/data 在/usr/目录下建立子目录 data&#xff0c;此时/usr 目录必须已经存在。rmdirrmdir dirname 删除空目录&#xff0c;目录里面如有文件或目录则…

c语言的输入函数有哪些

c语言的输入函数有&#xff1a;1、scanf的返回值scanf()函数返回成功赋值的数据项数&#xff0c;读到文件末尾出错时则返回EOF。如&#xff1a;scanf("%d%d", &a, &b);如果a和b都被成功读入&#xff0c;那么scanf的返回值就是2如果只有a被成功读入&#xff0…

php cdi_CDI和EJB:在事务成功时发送异步邮件

php cdi再次问好&#xff01; :) 这次&#xff0c;我选择了一项常见任务&#xff0c;我认为大多数情况下都以错误的方式完成&#xff1a;发送电子邮件。 并非所有人都不知道电子邮件API的工作方式&#xff0c;例如JavaMail或Apache的commons-email 。 我通常看到的一个问题是&…

linux中多进程调试,linux下用gdb调试多进程

今天来学习一下linux下gdb如何调试多进程&#xff0c;在学习之前我我们能先看一张表&#xff1a;这张表是gdb调试的命令表&#xff0c;这对那些对gdb不熟的同学来说是非常有必要的。一、多进程调试的命令1、set follow-fork-mode parent|child因为gdb在一般情况下&#xff0c;只…

初学者宝典:C语言入门基础知识大全(下)

06类型的自动转换和强制转换当同一表达式中各数据的类型不同时&#xff0c;编译程序会自动把它们转变成同一类型后再进行计算。转换优先级为&#xff1a;char < int < float < double 即左边级别“低“的类型向右边转换。具体地说&#xff0c;若在表达式中优先级最高的…

linux接口 头文件,第一种:1、添加关键头文件:#include linux/of_gpio.h#include linux/gpio.h...

第一种&#xff1a;1、添加关键头文件&#xff1a;#include #include #include #include #include #include 2、在已经存在驱动文件中搜索"DEVICE_ATTR"关键字&#xff0c;如果存在&#xff0c;直接参考已经存在的方法添加一个即可&#xff0c;如下&#xff1a;unsig…

viewpager默认界面_使用默认方法的界面演变–第一部分:方法

viewpager默认界面几周前&#xff0c;我们详细研究了默认方法 -Java 8中引入的一项功能&#xff0c;该功能允许为接口方法提供实现&#xff0c;即方法主体&#xff0c;从而定义接口中的行为。 引入此功能是为了实现接口演进 。 在JDK的上下文中&#xff0c;这意味着在不破坏所…

C语言中scanf函数的3种常见问题与应对技巧

在写代码时难免对一些知识点不熟悉&#xff0c;导致犯错&#xff0c;今天分享几点小知识给大家。空白符问题#includeint main(void){int a;printf("input the data ");scanf("%d ",&a); //这里多了一个回车符printf("%d",a);return 0;}结果…

jpa和hibernate_JPA和Hibernate级联类型的初学者指南

jpa和hibernate介绍 JPA将实体状态转换转换为数据库DML语句。 由于对实体图进行操作很常见&#xff0c;因此JPA允许我们将实体状态更改从父级传播到子级 。 通过CascadeType映射配置此行为。 JPA与Hibernate级联类型 Hibernate支持所有JPA级联类型和一些其他旧式级联样式。 下…

linux find 权限不够,超级用户find: `/home/pipi/.gvfs': 权限不够

用sudo su命令切换成的根用户&#xff0c;在找某文件的时候报错&#xff1a;rootubuntu:/home/pipi# find / -perm -2000/sbin/unix_chkpwdfind: /home/pipi/.gvfs: 权限不够就是普通用户pipi的主目录下的一个叫 .gvfs 的目录&#xff0c;dr-x------ 2 pipi pipi 0 …

aws上部署hadoop_在AWS Elastic MapReduce上运行PageRank Hadoop作业

aws上部署hadoop在上一篇文章中&#xff0c;我描述了一个执行PageRank计算的示例&#xff0c;该示例是使用Apache Hadoop进行Mining Massive Dataset课程的一部分。 在那篇文章中&#xff0c;我接受了Java中现有的Hadoop作业&#xff0c;并做了一些修改&#xff08;添加了单元测…

linux编写一个简单的端口扫描程序,小弟我在linux下写了个简单的多线程端口扫描程序,运行时出现有关问题,请问一下(2)...

当前位置:我的异常网 Linux/Unix 小弟我在linux下写了个简单的多线程端口扫描程序&#xff0c;小弟我在linux下写了个简单的多线程端口扫描程序&#xff0c;运行时出现有关问题,请问一下(2)www.myexceptions.net 网友分享于&#xff1a;2013-02-26 浏览&#xff1a;23次usle…

在嵌套使用if语句时,C语言规定else总是什么?

C语言的语法规定&#xff1a;else子句总是与前面最近的不带else的if相结合&#xff0c;与书写格式无关。在C语言中&#xff0c;使用if和else关键字对条件进行判断。请先看下面的代码&#xff1a;#include int main(){ int age; printf("请输入你的年龄&#xff1a;&…

optional空值判断_Java 8 Optional不仅用于替换空值

optional空值判断总览 在Java 8中&#xff0c;您可以返回Optional而不是返回null。 就像您在Java 7中所做的那样。这可能会有所不同&#xff0c;这取决于您是否倾向于忘记检查null还是使用静态代码分析来检查nullalbe引用。 但是&#xff0c;还有一种更引人注目的情况是将Opti…

continue语句的作用是结束整个循环的执行吗?

continue 语句的作用是结束本次循环&#xff0c;跳过循环体中剩余的语句而强制进入下一次循环&#xff08;回到循环体的开头准备再次执行循环体&#xff09;。continue语句只用在 while、for 循环中&#xff0c;常与 if 条件语句一起使用&#xff0c;判断条件是否成立。使用方式…

linux 远程权限不够,Eclipse连接远程Hadoop集群开发时权限不足问题解决方案

eclipse连接远程Hadoop集群开发时报错Exception in thread "main" org.apache.hadoop.security.AccessControlException: Permission denied: userd, accessWRITE, inode"data":zxg:supergroup:rwxr-xr-xat org.apache.hadoop.hdfs.server.namenode.FSPerm…

jsp导入jstl标签库_EE JSP:使用JSTL标记库生成动态内容

jsp导入jstl标签库除了在JSP中编写自己的定制标记之外&#xff0c;您还将发现Java EE实际上提供了一组Java标准标记库&#xff08;JSTL&#xff09;供您使用。 这些内置标签包括重复&#xff08;for-loop&#xff09;标签&#xff0c;条件标签&#xff0c;变量声明和输出标签等…

一文掌握 C 智能指针的使用

RAII 与引用计数了解 objective-C/Swift 的程序员应该知道引用计数的概念。引用计数这种计数是为了防止内存泄露而产生的。基本想法是对于动态分配的对象&#xff0c;进行引用计数&#xff0c;每当增加一次对同一个对象的引用&#xff0c;那么引用对象的引用计数就会增加一次&a…