Skip to content

Commit 08d1f23

Browse files
Stefan Rempferjgrandja
authored andcommitted
Add redis based authorization code services
Fixes spring-atticgh-935
1 parent 38bd24a commit 08d1f23

File tree

2 files changed

+249
-0
lines changed

2 files changed

+249
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.provider.code;
17+
18+
import java.lang.reflect.Method;
19+
import java.util.List;
20+
21+
import org.springframework.data.redis.connection.RedisConnection;
22+
import org.springframework.data.redis.connection.RedisConnectionFactory;
23+
import org.springframework.security.oauth2.provider.OAuth2Authentication;
24+
import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy;
25+
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy;
26+
import org.springframework.util.ClassUtils;
27+
import org.springframework.util.ReflectionUtils;
28+
29+
/**
30+
* Implementation of authorization code services that stores the codes and authentication in Redis.
31+
*
32+
* <p>
33+
* @deprecated See the <a href="https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide">OAuth 2.0 Migration Guide</a> for Spring Security 5.
34+
*
35+
* @author Stefan Rempfer
36+
*/
37+
@Deprecated
38+
public class RedisAuthorizationCodeServices extends RandomValueAuthorizationCodeServices {
39+
40+
private static final boolean springDataRedis_2_0 = ClassUtils.isPresent(
41+
"org.springframework.data.redis.connection.RedisStandaloneConfiguration",
42+
RedisAuthorizationCodeServices.class.getClassLoader());
43+
44+
private static final String AUTH_CODE = "auth_code:";
45+
46+
private final RedisConnectionFactory connectionFactory;
47+
48+
private String prefix = "";
49+
50+
private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();
51+
52+
private Method redisConnectionSet_2_0;
53+
54+
/**
55+
* Default constructor.
56+
*
57+
* @param connectionFactory the connection factory which should be used to obtain a connection to Redis
58+
*/
59+
public RedisAuthorizationCodeServices(RedisConnectionFactory connectionFactory) {
60+
this.connectionFactory = connectionFactory;
61+
if (springDataRedis_2_0) {
62+
this.loadRedisConnectionMethods_2_0();
63+
}
64+
}
65+
66+
@Override
67+
protected void store(String code, OAuth2Authentication authentication) {
68+
byte[] key = serializeKey(AUTH_CODE + code);
69+
byte[] auth = serialize(authentication);
70+
71+
RedisConnection conn = getConnection();
72+
try {
73+
if (springDataRedis_2_0) {
74+
try {
75+
this.redisConnectionSet_2_0.invoke(conn, key, auth);
76+
} catch (Exception ex) {
77+
throw new RuntimeException(ex);
78+
}
79+
} else {
80+
conn.set(key, auth);
81+
}
82+
}
83+
finally {
84+
conn.close();
85+
}
86+
}
87+
88+
@Override
89+
protected OAuth2Authentication remove(String code) {
90+
byte[] key = serializeKey(AUTH_CODE + code);
91+
92+
List<Object> results = null;
93+
RedisConnection conn = getConnection();
94+
try {
95+
conn.openPipeline();
96+
conn.get(key);
97+
conn.del(key);
98+
results = conn.closePipeline();
99+
}
100+
finally {
101+
conn.close();
102+
}
103+
104+
if (results == null) {
105+
return null;
106+
}
107+
byte[] bytes = (byte[]) results.get(0);
108+
return deserializeAuthentication(bytes);
109+
}
110+
111+
private void loadRedisConnectionMethods_2_0() {
112+
this.redisConnectionSet_2_0 = ReflectionUtils.findMethod(
113+
RedisConnection.class, "set", byte[].class, byte[].class);
114+
}
115+
116+
private byte[] serializeKey(String object) {
117+
return serialize(prefix + object);
118+
}
119+
120+
private byte[] serialize(Object object) {
121+
return serializationStrategy.serialize(object);
122+
}
123+
124+
private byte[] serialize(String string) {
125+
return serializationStrategy.serialize(string);
126+
}
127+
128+
private RedisConnection getConnection() {
129+
return connectionFactory.getConnection();
130+
}
131+
132+
private OAuth2Authentication deserializeAuthentication(byte[] bytes) {
133+
return serializationStrategy.deserialize(bytes, OAuth2Authentication.class);
134+
}
135+
136+
public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) {
137+
this.serializationStrategy = serializationStrategy;
138+
}
139+
140+
public void setPrefix(String prefix) {
141+
this.prefix = prefix;
142+
}
143+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.provider.code;
17+
18+
import static org.hamcrest.CoreMatchers.allOf;
19+
import static org.hamcrest.CoreMatchers.containsString;
20+
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertNotEquals;
22+
import static org.junit.Assert.assertNotNull;
23+
import static org.junit.Assert.assertNotSame;
24+
import static org.junit.Assert.assertThat;
25+
import static org.junit.Assert.fail;
26+
27+
import org.junit.Before;
28+
import org.junit.Test;
29+
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
30+
import org.springframework.security.authentication.TestingAuthenticationToken;
31+
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
32+
import org.springframework.security.oauth2.provider.OAuth2Authentication;
33+
import org.springframework.security.oauth2.provider.RequestTokenFactory;
34+
35+
import org.springframework.util.ClassUtils;
36+
import redis.clients.jedis.JedisShardInfo;
37+
38+
/**
39+
* @author Stefan Rempfer
40+
*/
41+
public class RedisAuthorizationCodeServicesTests {
42+
43+
private RedisAuthorizationCodeServices authorizationCodeServices;
44+
45+
private OAuth2Authentication authentication;
46+
47+
/**
48+
* Initialize test data and Class-Under-Test.
49+
*/
50+
@Before
51+
public void setup() {
52+
boolean springDataRedis_2_0 = ClassUtils.isPresent(
53+
"org.springframework.data.redis.connection.RedisStandaloneConfiguration",
54+
this.getClass().getClassLoader());
55+
56+
JedisConnectionFactory connectionFactory;
57+
if (springDataRedis_2_0) {
58+
connectionFactory = new JedisConnectionFactory();
59+
} else {
60+
JedisShardInfo shardInfo = new JedisShardInfo("localhost");
61+
connectionFactory = new JedisConnectionFactory(shardInfo);
62+
}
63+
64+
authorizationCodeServices = new RedisAuthorizationCodeServices(connectionFactory);
65+
66+
authentication = new OAuth2Authentication(RequestTokenFactory.createOAuth2Request("myClientId", false),
67+
new TestingAuthenticationToken("myUser4Test", false));
68+
}
69+
70+
/**
71+
* Verifies that a authorization code could be generated and stored.
72+
*/
73+
@Test
74+
public void verifyCreateAuthorizationCode() {
75+
String authorizationCode1 = authorizationCodeServices.createAuthorizationCode(authentication);
76+
assertNotNull("Authorization code must not be null!", authorizationCode1);
77+
78+
String authorizationCode2 = authorizationCodeServices.createAuthorizationCode(authentication);
79+
assertNotNull("Authorization code must not be null!", authorizationCode2);
80+
81+
assertNotEquals("Authorization code must be different!", authorizationCode1, authorizationCode2);
82+
}
83+
84+
/**
85+
* Verifies that a authorization code could be retrieved and removed.
86+
*/
87+
@Test
88+
public void verifyCreateAndConsumeAuthorizationCode() {
89+
90+
String authorizationCode = authorizationCodeServices.createAuthorizationCode(authentication);
91+
assertNotNull("Authorization code must not be null!", authorizationCode);
92+
93+
OAuth2Authentication authentication = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);
94+
assertNotSame("Authentication object must not be the same!", this.authentication, authentication);
95+
assertEquals("Authentication object must equals to original one!", this.authentication, authentication);
96+
97+
try {
98+
authorizationCodeServices.consumeAuthorizationCode(authorizationCode);
99+
fail("There must be an exception that the authorization code is invalid!");
100+
}
101+
catch (InvalidGrantException e) {
102+
assertThat("Wrong error message!", e.getMessage(),
103+
allOf(containsString("Invalid"), containsString(authorizationCode)));
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)