spring期刊状态_无状态Spring安全性第2部分:无状态认证

spring期刊状态

Spring Stateless Security系列的第二部分是关于以无状态方式探索身份验证的方法。 如果您错过了有关CSRF的第一部分,可以在这里找到。

因此,在谈论身份验证时,其全部内容就是让客户端以可验证的方式向服务器标识自己。 通常,这始于服务器向客户端提供挑战,例如要求填写用户名/密码的请求。 今天,我想集中讨论通过这种初始(手动)挑战后会发生什么,以及如何处理其他HTTP请求的自动重新身份验证。

常用方法

基于会话Cookie

我们可能最了解的最常见方法是使用服务器生成的JSESSIONID cookie形式的秘密令牌(会话密钥)。 这些天的初始设置几乎没有用,也许会让您忘记,您有一个选择要首先在这里进行。 即使不再使用此“会话密钥”来存储“会话中”的任何其他状态,该密钥本身实际上也是状态 。 即,如果没有这些密钥的共享和持久性存储,则成功的身份验证将无法在服务器重新启动或请求负载平衡到另一台服务器后继续存在。

OAuth2 / API密钥

每当谈论REST API和安全性时; 提到了OAuth2和其他类型的API密钥。 基本上,它们涉及在HTTP授权标头中发送自定义令牌/密钥。 如果使用得当,两种方法都可以避免客户端使用标头来处理Cookie。 这解决了CSRF漏洞和其他Cookie相关问题。 但是,他们没有解决的一件事是服务器需要检查显示的身份验证密钥,这几乎需要一些持久且可维护的共享存储来将密钥链接到用户/授权。

无状态方法

1. HTTP基础认证

处理认证的最古老,最粗糙的方式。 只需让用户在每次请求时发送其用户名/密码即可。 这听起来似乎很可怕,但是考虑到上述任何方法也都通过网​​络发送秘密密钥,这实际上并不是那么安全。 主要是用户体验和灵活性,这使得其他方法成为更好的选择。

2.服务器签名的令牌

以无状态方式处理请求中的状态的一个巧妙小技巧是让服务器对其“签名”。 然后可以在每个请求之间在客户端/服务器之间来回传输,并确保它不会被调和。 这样,任何用户标识数据都可以以纯文本形式共享,并为其添加特殊的签名哈希。 考虑到已签名,服务器可以简单地验证签名哈希是否仍与接收到的内容匹配,而无需保持任何服务器端状态。

可以用于此目的的通用标准是JSON Web令牌 (JWT),该标准仍在起草中。 对于本博客文章,我想摆脱困境,跳过完全的合规性以及使用它附带的库的尖叫声。 从中挑选我们真正需要的东西。 (省略了标头/变量哈希算法和url-safe base64编码)

实作

如前所述,我们将使用Spring Security和Spring Boot将自己的实现整合在一起。 没有任何库或精美的API会混淆令牌级别上真正发生的事情。 令牌在伪代码中看起来像这样:

content = toJSON(user_details)
token = BASE64(content) + "." + BASE64(HMAC(content))

令牌中的用作分隔符,因此每个部分都可以分别标识和解码,因为点字符不是任何base64编码字符串的一部分。 HMAC代表基于哈希的消息身份验证代码,它基本上是使用预定义密钥从任何数据中生成的哈希。

在实际的Java中,令牌的生成看起来很像伪代码:

创建令牌

public String createTokenForUser(User user) {byte[] userBytes = toJSON(user);byte[] hash = createHmac(userBytes);final StringBuilder sb = new StringBuilder(170);sb.append(toBase64(userBytes));sb.append(SEPARATOR);sb.append(toBase64(hash));return sb.toString();
}

JSON中使用的相关User属性是id,username,expires和role ,但可以是您真正想要的任何东西。 我标记了杰克逊JSON序列化期间将忽略的User对象的“ password”属性,因此它不会成为令牌的一部分:

忽略密码

@JsonIgnore
public String getPassword() {return password;
}

对于现实世界的场景,您可能只想为此使用专用对象。

使用一些输入验证来防止/捕获由于对令牌进行调整而导致的解析错误,令牌的解码会稍微复杂一些:

解码令牌

public User parseUserFromToken(String token) {final String[] parts = token.split(SEPARATOR_SPLITTER);if (parts.length == 2 && parts[0].length() > 0 && parts[1].length() > 0) {try {final byte[] userBytes = fromBase64(parts[0]);final byte[] hash = fromBase64(parts[1]);boolean validHash = Arrays.equals(createHmac(userBytes), hash);if (validHash) {final User user = fromJSON(userBytes);if (new Date().getTime() < user.getExpires()) {return user;}}} catch (IllegalArgumentException e) {//log tampering attempt here}}return null;
}

它本质上验证提供的哈希值是否与内容的新计算哈希值相同。 因为createHmac方法在内部使用未公开的秘密密钥来计算哈希,所以没有客户端能够调整内容并提供与服务器生成的哈希相同的哈希。 仅在通过此测试后,提供的数据才会被解释为表示User对象的JSON。

放大Hmac部分,让我们看一下所涉及的Java。 首先,必须使用一个私钥对其进行初始化,这是TokenHandler的构造函数的一部分:

HMAC初始化

...
private static final String HMAC_ALGO = "HmacSHA256";private final Mac hmac;public TokenHandler(byte[] secretKey) {try {hmac = Mac.getInstance(HMAC_ALGO);hmac.init(new SecretKeySpec(secretKey, HMAC_ALGO));} catch (NoSuchAlgorithmException | InvalidKeyException e) {throw new IllegalStateException("failed to initialize HMAC: " + e.getMessage(), e);}
}
...

初始化后,可以使用一个方法调用(重新)使用它! (doFinal的JavaDoc读取“处理给定的字节数组并完成MAC操作。对该方法的调用会将这个Mac对象重置为先前通过调用init(Key)或init(Key,AlgorithmParameterSpec进行初始化)时所处的状态。 …”)

createHmac

// synchronized to guard internal hmac object
private synchronized byte[] createHmac(byte[] content) {return hmac.doFinal(content);
}

我在这里使用了一些粗略的同步,以防止在Spring Singleton Service中使用时发生冲突。 实际的方法非常快(〜0.01ms),因此除非您每台服务器每秒要发送10k +请求,否则它不会造成任何问题。

说到服务,让我们一路攀升到完全可运行的基于令牌的身份验证服务:

令牌认证服务

@Service
public class TokenAuthenticationService {private static final String AUTH_HEADER_NAME = "X-AUTH-TOKEN";private static final long TEN_DAYS = 1000 * 60 * 60 * 24 * 10;private final TokenHandler tokenHandler;@Autowiredpublic TokenAuthenticationService(@Value("${token.secret}") String secret) {tokenHandler = new TokenHandler(DatatypeConverter.parseBase64Binary(secret));}public void addAuthentication(HttpServletResponse response, UserAuthentication authentication) {final User user = authentication.getDetails();user.setExpires(System.currentTimeMillis() + TEN_DAYS);response.addHeader(AUTH_HEADER_NAME, tokenHandler.createTokenForUser(user));}public Authentication getAuthentication(HttpServletRequest request) {final String token = request.getHeader(AUTH_HEADER_NAME);if (token != null) {final User user = tokenHandler.parseUserFromToken(token);if (user != null) {return new UserAuthentication(user);}}return null;}
}

很简单,初始化一个私有TokenHandler来完成繁重的工作。 它提供了添加和读取自定义HTTP令牌标头的方法。 如您所见,它不使用任何(数据库驱动的)UserDetailsS​​ervice查找用户详细信息。 通过令牌提供了让Spring Security处理进一步的授权检查所需的所有详细信息。
最后,我们现在可以将所有这些插件插入到Spring Security中,在Security配置中添加两个自定义过滤器:

StatelessAuthenticationSecurityConfig内部的安全性配置

...
@Override
protected void configure(HttpSecurity http) throws Exception {http...// custom JSON based authentication by POST of // {"username":"<name>","password":"<password>"} // which sets the token header upon authentication.addFilterBefore(new StatelessLoginFilter("/api/login", ...), UsernamePasswordAuthenticationFilter.class)// custom Token based authentication based on // the header previously given to the client.addFilterBefore(new StatelessAuthenticationFilter(...), UsernamePasswordAuthenticationFilter.class);
}
...

StatelessLoginFilter在成功认证后添加令牌:

StatelessLoginFilter

...
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,FilterChain chain, Authentication authentication) throws IOException, ServletException {// Lookup the complete User object from the database and create an Authentication for itfinal User authenticatedUser = userDetailsService.loadUserByUsername(authentication.getName());final UserAuthentication userAuthentication = new UserAuthentication(authenticatedUser);// Add the custom token as HTTP header to the responsetokenAuthenticationService.addAuthentication(response, userAuthentication);// Add the authentication to the Security contextSecurityContextHolder.getContext().setAuthentication(userAuthentication);
}
...

StatelessAuthenticationFilter仅根据标头设置身份验证:

StatelessAuthenticationFilter

...
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,	ServletException {SecurityContextHolder.getContext().setAuthentication(tokenAuthenticationService.getAuthentication((HttpServletRequest) req));chain.doFilter(req, res); // always continue
}
...

请注意,与大多数与Spring Security相关的过滤器不同,无论身份验证是否成功,我都选择继续沿过滤器链向下移动。 我想支持触发Spring的AnonymousAuthenticationFilter以支持匿名身份验证。 这里最大的区别是过滤器未配置为映射到任何专门用于身份验证的URL,因此不提供标头并不是真正的问题。

客户端实施

客户端实现同样非常简单。 再次,我将其保持为简约,以防止在AngularJS详细信息中丢失身份验证位。 如果您正在寻找一个更完整地与路由集成的AngularJS JWT示例,则应在此处查看 。 我从中借用了一些拦截器逻辑。
登录只需存储令牌(在localStorage中 ):

登录

$scope.login = function () {var credentials = { username: $scope.username, password: $scope.password };$http.post('/api/login', credentials).success(function (result, status, headers) {$scope.authenticated = true;TokenStorage.store(headers('X-AUTH-TOKEN'));});  
};

注销甚至更简单(无需调用服务器):

登出

$scope.logout = function () {// Just clear the local storageTokenStorage.clear();	$scope.authenticated = false;
};

要检查用户是否“已经登录”,ng-init =“ init()”可以很好地工作:

在里面

$scope.init = function () {$http.get('/api/users/current').success(function (user) {if(user.username !== 'anonymousUser'){$scope.authenticated = true;$scope.username = user.username;}});
};

我选择使用匿名可访问的端点来防止触发401/403。 您还可以解码令牌本身并检查到期时间,并相信本地客户端时间足够准确。

最后,为了使添加标头的过程自动化,就像上一个博客条目中那样,一个简单的拦截器做得很好:

令牌验证拦截器

factory('TokenAuthInterceptor', function($q, TokenStorage) {return {request: function(config) {var authToken = TokenStorage.retrieve();if (authToken) {config.headers['X-AUTH-TOKEN'] = authToken;}return config;},responseError: function(error) {if (error.status === 401 || error.status === 403) {TokenStorage.clear();}return $q.reject(error);}};
}).config(function($httpProvider) {$httpProvider.interceptors.push('TokenAuthInterceptor');
});

如果客户端不会允许对需要更高特权的区域进行调用,它还将负责在收到HTTP 401或403之后自动清除令牌。

令牌存储

TokenStorage只是对localStorage的包装服务,我不会打扰您。 将令牌放到localStorage中可以防止脚本像保存cookie一样在保存脚本的脚本源之外读取脚本。 但是,由于令牌不是实际的Cookie,因此无法指示任何浏览器将其自动添加到请求中。 这是至关重要的,因为它可以完全防止任何形式的CSRF攻击。 因此,您不必实施我以前的博客中提到的任何(无状态)CSRF保护。

  • 您可以在github上找到一个完整的工作示例,其中包含一些不错的功能。

确保已安装gradle 2.0,并使用“ gradle build”和“ gradle run”简单地运行它。 如果要像Eclipse一样在IDE中使用它,请使用“ gradle eclipse”,然后从IDE内导入并运行它(不需要服务器)。

翻译自: https://www.javacodegeeks.com/2014/10/stateless-spring-security-part-2-stateless-authentication.html

spring期刊状态

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

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

相关文章

分享10个值得关注的C语言开源项目

来源于网络&#xff0c;如有侵权&#xff0c;告知必删。

python3 x默认使用的编码_python3默认使用什么编码

python3默认编码为unicode&#xff0c;由str类型进行表示。二进制数据使用byte类型表示。 字符串通过编码转换成字节码&#xff0c;字节码通过解码成为字符串encode&#xff1a;str --> bytes&#xff08;推荐学习&#xff1a;Python视频教程&#xff09; decode&#xff1a…

html中写css代码,开发DIV CSS时 先写CSS代码还是先写HTML代码

相信良多&#xff2c;&#xff2f;&#xff36;&#xff25;用DIVCSS技术启示重构网页的爱好者友好&#xff0c;在起源学习DIVCSS的时分都邑想一个标题&#xff0c;想晓得DIVCSS妙手或有教育者在开发制作html页面的时刻&#xff0c;下场是先写html照样先写css&#xff1f;带着这…

象棋子 设计模式_通过设计国际象棋游戏了解策略模式

象棋子 设计模式今天&#xff0c;我们将借助一个示例来尝试了解策略模式。 我们将考虑的示例是国际象棋游戏。 这里的目的是解释策略模式&#xff0c;而不是构建全面的国际象棋游戏解决方案。 策略模式&#xff1a;策略模式被称为行为模式–用于管理对象之间的算法&#xff0…

入门C语言10问10答

1 如何理解变量与常量?变量与常量相当于数据的可读可写与只读&#xff0c;常量是数据的一种保护机制。在内存分配给程序的内存块中有专门的常量&#xff08;只读&#xff09;存储区。2 整型数据的溢出问题任何一种数据类型的数据在计算机中都有它确定的数值表示范围&#xff0…

mac json格式化工具_简洁好用的工具都是相似的

大家好&#xff0c;我是你们的章鱼猫。不知道大家了不了解 jq 这个工具呢&#xff1f;指的不是 JQuery&#xff0c;而是一个命令行工具。jq 是一个轻量级而且灵活的命令行 JSON 解析器&#xff0c;类似用于 JSON 数据的 sed 工具。我们来看一下使用 jq 处理 json 的基本用法(更…

android 撑满剩余空间,怎么让LinearLayout占据父布局的所有剩余空间

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼xmlns:tools"http://schemas.android.com/tools"android:layout_width"match_parent"android:layout_height"match_parent"android:paddingBottom"dimen/activity_vertical_margin"androi…

战神4 幕后花絮 概念艺术_Java 9幕后花絮:新功能从何而来?

战神4 幕后花絮 概念艺术找出Java幕后发生的事情&#xff0c;以及新功能如何实现 在上一篇文章中&#xff0c;我们介绍了即将发布的Java 9版本的新功能和尚待解决的功能&#xff0c;并简要提到了将新功能添加到下一个版本之前要经历的过程。 由于此过程几乎影响了所有Java开发人…

ID生成器 雪花算法

背景&#xff1a;在很多业务场景下&#xff0c;我们都需要一个唯一的 ID 来进行一些数据的交互&#xff0c;那么如何生成这个唯一的 ID 呢&#xff1f;如果在单机的情况下&#xff0c;生成唯一ID&#xff0c;可以利用机器内存的特点&#xff0c;通过内存分配即可。但我们线上的…

2014 android 机型排行,2014年10月十佳Android系统智能安卓手机排行榜单 Note 4第一名...

Android系统手机经过几年的发展&#xff0c;如今已经成了大家购机的首选。但在市面上数量众多的Android家族成员中&#xff0c;那些才是真正值得期待和拥有的机型&#xff0c;或许每个人会有不同的答案。如果你还在几款机型的选择上纠结&#xff0c;那么不妨借助国外媒体Androi…

python anaconda安装_Python - 安装并配置Anaconda环境

$ py --version # 当前默认python版本 Python 3.7.1 $ conda create --name testpy2 python2.7 pandas # 创建名为testpy2的运行环境&#xff0c;并安装pandas包及其依赖包 Solving environment: done ## Package Plan ## environment location: D:\DownLoadFiles\anaconda3\en…

jstack调试_增压的jstack:如何以100mph的速度调试服务器

jstack调试使用jstack调试实时Java生产服务器的指南 jstack就像U2一样-从时间的黎明就一直在我们身边&#xff0c;我们似乎无法摆脱它 。 除了笑话&#xff0c;到目前为止&#xff0c;jstack是您的工具库中用于调试实时生产服务器的最方便的工具之一。 即便如此&#xff0c;我仍…

C/C 输入输出缓冲区

【导读】&#xff1a;本文介绍C与C 输入输出缓冲的一些操作与特性。以下是正文&#xff08;1&#xff09;c 中cin、cout&#xff0c;cerr和c的stdin、stdout、stderr都是同步的&#xff0c;即iostream 对象和 and cstdio流是同步的&#xff0c;同步关系如下&#xff1a;同步即表…

android 活动销毁不了,即使活动已经销毁,AsyncTask也不会停止

我想添加一个信息补充&#xff0c;并给出一个指向库或2的指针&#xff0c;可以用于长时间运行的AsyncTask&#xff0c;甚至可以用于面向网络的asynctasks。AsyncTasks专为在后台执行操作而设计。是的&#xff0c;您可以使用该cancel方法停止它。当您从Internet下载内容时&#…

python输入input数组_python怎么输入数组

python怎么输入数组&#xff1f; python输入数组 一维数组&#xff1a;arr input("") //输入一个一维数组&#xff0c;每个数之间使空格隔开 num [int(n) for n in arr.split()] //将输入每个数以空格键隔开做成数组 print(num) //打印数组 一维数组输入输出示例&a…

eclipse 扩展_Eclipse扩展的轻量级集成测试

eclipse 扩展最近&#xff0c;我为Eclipse扩展点评估引入了一个小助手。 辅助程序努力减少通用编程步骤的样板代码&#xff0c;同时增加开发指导和可读性。 这篇文章是希望的后续文章&#xff0c;它显示了如何将实用程序与AssertJ定制断言结合使用&#xff0c;以编写针对Eclip…

深入理解右值引用,move语义和完美转发

move语义最原始的左值和右值定义可以追溯到C语言时代&#xff0c;左值是可以出现在赋值符的左边和右边&#xff0c;然而右值只能出现在赋值符的右边。在C 里&#xff0c;这种方法作为初步判断左值或右值还是可以的&#xff0c;但不只是那么准确了。你要说C 中的右值到底是什么&…

android startanimation 回调,ScheduledThreadPoolExecutor执行莫名停止问题Android几个动画回调运行线程...

本文记录两个问题&#xff1a;ScheduleThreadPoolExecutor莫名停止执行。Animation和Animator两个动画回调监听 运行在哪个线程。一&#xff1a;ScheduleThreadPoolExecutor问题&#xff1a;ScheduledThreadPoolExecutor中scheduleWithFixedDelay(command, initialDelay, delay…

java future用法_纯干货:Java学习过程中的21个知识点和技术点

我们在Java学习过程中要学会抓重点&#xff0c;善于总结&#xff0c;Java学习过程中常见的21个知识点和技术点你知道吗&#xff1f;下面和千锋广州小编一起来看看吧&#xff01;1. JVM相关对于刚刚接触Java的人来说&#xff0c;JVM相关的知识不一定需要理解很深&#xff0c;对此…

zz测试接口_使用FizzBu​​zz和JUnitParams进行单元测试

zz测试接口我有时使用FizzBu​​zz向新手演示单元测试的基础。 尽管FizzBu​​zz确实是一个简单的问题&#xff0c;但它也可以用于演示更高级的单元测试技术&#xff0c;例如实现参数化测试。 FizzBu​​zz的可能解决方案之一是&#xff1a; public class FizzBuzz {private …