基于Spring Boot,Security和JWB的REST接口的无状态认证

Stateless Spring Security Part 2: Stateless Authentication


This second part of the Stateless Spring Security series is about exploring means of authentication in a stateless way. If you missed the first part about CSRF you can find it here.

So when talking about Authentication, its all about having the client identify itself to the server in a verifiable manner. Typically this start with the server providing the client with a challenge, like a request to fill in a username / password. Today I want to focus on what happens after passing such initial (manual) challenge and how to deal with automatic re-authentication of futher HTTP requests.

Common approaches

SESSION COOKIE BASED

The most common approach we probably all know is to use a server generated secret token (Session key) in the form of a JSESSIONID cookie. Initial setup for this is near nothing these days perhaps making you forget you have a choice to make here in the first place. Even without further using this “Session key” to store any other state “in the session”, the key itself is in fact state as well.  I.e. without a shared and persistent storage of these keys, no successful authentication will survive a server reboot or requests being load balanced to another server.

OAUTH2 / API KEYS

Whenever talking about REST APIs and Security; OAuth2 and other types of API keys are mentioned. Basically they involve sending custom tokens/keys within the HTTP Authorization header. When used properly both relieve clients from dealing with Cookies using the header instead. This solves CSRF vulnerabilities and other Cookie related issues. One thing they do not solve however is the need for the server to check the presented authentication keys, pretty much demanding some persistent and maintainable shared storage for linking the keys to users/authorizations.

Stateless approaches

1. HTTP BASIC AUTH

The oldest and most crude way of dealing with authentication. Simply have the user send its username/password with every request. This probably sounds horrible, but considering any of the approaches mentioned above also send secret keys over the wire, this isn’t really all that less secure at all. Its mainly the user experience and flexibility that makes the other approaches a better choice.

2. SERVER SIGNED TOKENS

A neat little trick to dealing with state across requests in a stateless way is to have the server “sign” it. It can then be transported back and forth between the client/server each request with the guarantee that it is not tampered with. This way any user identification data can be shared in plain-text, adding a special signing hash to it. Considering it is signed, the server can simply validate if the signing hash still matches the received content, without needing to hold any server-side state.

The common standard that can be used for this is JSON Web Tokens (JWT) which is still in draft. For this blog post I’d like to get down and dirty though, skipping full compliance and the scream for using a library that comes with it. Picking just what we actually need from it. (Leaving out the header/variable hash algoritms and url-safe base64 encoding)


Implementation

As mentioned we’re going to roll our own implementation, using Spring Security and Spring Boot to plug it all together. Without any library or fancy API obfuscating what’s really happening on the token level. The token is going to look like this in pseudo-code:

The dot in the token serves as a separator, so each part can be identified and decoded separately as the dot character is not part of any base64 encoded string. The HMAC stands for a Hash-based Message Authentication Code, which is basically a hash made from any data using a predefined secret key.

In actual Java the generation of the token looks a lot like the pseudo-code:

The relevant User properties used in the JSON are id, username, expires and roles, but could be anything you want really. I marked the “password” property of the User object to be ignored during jackson JSON serialization so it does not become part of the token:

For real worlds scenarios you probably just want to use a dedicated object for this.

The decoding of the token is a bit more complex with some input validation to prevent/catch parsing errors due to tampering with the token:

It essentially validates if the provided hash is the same as a fresh computed hash of the content.Because the createHmac method uses an undisclosed secret key internally to compute the hash, no client will be able to tamper with the content and provide a hash that is the same as the one the server will produce. Only after passing this test the provided data will be interpreted as JSON representing a User object.

Zooming in on the Hmac part, lets see the exact Java involved. First it must be initialized with a secret key, which I do as part of TokenHandler’s constructor:

After initialization it can be (re-)used, using a single method call! (doFinal’s JavaDoc reads “Processes the given array of bytes and finishes the MAC operation. A call to this method resets this Mac object to the state it was in when previously initialized via a call to init(Key) or init(Key, AlgorithmParameterSpec)…”)

I used some crude synchronization here, to prevent conflicts when used within a Spring Singleton Service. The actual method is very fast (~0.01ms) so it shouldn’t cause a problem unless your going for 10k+ requests per seconds per server.

Speaking of the Service, lets work our way up to a fully working token-based authentication service:

Pretty straight-forward, initializing a private TokenHandler to do the heavy lifting. It provides methods for adding and reading the custom HTTP token header. As you can see it does not use any (database driven) UserDetailsService to lookup the user details. All details required to let Spring Security handle further authorization checks are provided by means of the token.
Finally we can now plug-in all of this into Spring Security adding two custom filters in the Security configuration:

The StatelessLoginFilter adds the token upon successful authentication:

the StatelessAuthenticationFilter simply sets the authentication based upon the header:

Note that unlike most Spring Security related filters, I choose to continue down the filter chain regardless of successful authentication. I wanted to support triggering Spring’s  AnonymousAuthenticationFilter to support anonymous authentication. The big difference here being that the filter is not configured to map to any url specifically meant for authentication, so not providing the header isn’t really a fault.


 

CLIENT-SIDE IMPLEMENTATION

Client-side implementation is again pretty straight-forward. Again I’m keeping it minimalistic to prevent the authentication bit being lost in AngularJS details. If you’re looking for an AngularJS JWT example more thoroughly integrated with routes you should take a look here. I borrowed some of the interceptor logic from it.
Logging in, is simply a matter of storing the token (in localStorage):

Logging out is even simpler (no call to the server necessary):

To check if a user is “already logged in” ng-init=”init()” works nicely:

I choose to use an anonymously reachable endpoint to prevent triggering 401/403’s. You could also decode the token itself and check the expiration time, trusting the local client time to be accurate enough.

Finally in order to automate the process of adding the header a simple interceptor much like in last blog entry does nicely:

It also takes care of automatically clearing the token after receiving an HTTP 401 or 403, assuming the client isn’t going to allow calls to areas that need higher privileges.

TOKENSTORAGE

The TokenStorage is just a wrapper service over localStorage which I’ll not bother you with. Putting the token in the localStorage protects it from being read by script outside the origin of the script that saved it, just like cookies. However because the token is not an actual Cookie, no browser can be instructed add it to requests automatically. This is essential as it completely prevents any form of CSRF attacks. Thus saving you from having to implement any (Stateless) CSRF protection mentioned in my previous blog.


You can find a complete working example with some nice extras at github
Make sure you have gradle 2.0 installed and simply run it using “gradle build” followed by a “gradle run”. If you want to play with it in your IDE like Eclipse, go with “gradle eclipse” and just import and run it from within your IDE (no server needed).

 

 

This entry was posted in  AngularJSCodingFEFront EndJavaRESTSecurity by  Robbert van Waveren. Bookmark the  permalink.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值