我想通过spring安全性和Json Web Token(JWT)保护我的服务,并通过Spring session将登录的用户存储在redis中。因此,当用户发送登录请求时,我对用户进行身份验证并将生成的令牌发送回如下所示:
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtAuthenticationRequest authenticationRequest, Device device) throws AuthenticationException { // Perform the security final Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( authenticationRequest.getUsername(), authenticationRequest.getPassword() ) ); SecurityContextHolder.getContext().setAuthentication(authentication); final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); final String token = jwtTokenUtil.generateToken(userDetails, device); // Return the token return ResponseEntity.ok(new JwtAuthenticationResponse(token)); }
我也将该用户添加到会话(支持Redis会话)中:
HttpSession session = request.getSession(false); if (session != null) { session.setMaxInactiveInterval(30 * 600); JwtUser user = (JwtUser) authentication.getPrincipal(); session.setAttribute("user", user); }
之后我检查Redis的,我看已经被设置为redis.But当我收到来自用户下一次尝试的用户会话成为 anonymousUser ,理想情况shouldn’t.Reason这个问题的背后,是进入JSESSIONID无效莫名其妙。
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
我看到调试屏幕主体返回anonymousUser。
我也尝试如下:
@RequestMapping(value = "/username", method = RequestMethod.GET) @ResponseBody public String currentUserNameSimple(HttpServletRequest request) { Principal principal = request.getUserPrincipal(); return principal.getName(); }
但是principal为空。
我的redis配置:
@Configuration @EnableRedisHttpSession public class RedisConfig implements BeanClassLoaderAware { private ClassLoader loader; @Bean(name = { "defaultRedisSerializer", "springSessionDefaultRedisSerializer" }) public RedisSerializer<Object> springSessionDefaultRedisSerializer() { return new GenericJackson2JsonRedisSerializer(objectMapper()); } @Bean public HttpSessionStrategy httpSessionStrategy() { return new HeaderHttpSessionStrategy(); } @Bean JedisConnectionFactory jedisConnectionFactory() { JedisConnectionFactory jedisConFactory = new JedisConnectionFactory(); jedisConFactory.setHostName("localhost"); jedisConFactory.setPort(6379); return jedisConFactory; } ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModules(SecurityJackson2Modules.getModules(this.loader)); return mapper; } public void setBeanClassLoader(ClassLoader classLoader) { this.loader = classLoader; }
}
Maven依赖项:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <version>1.4.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core --> <!-- Password Validation --> <dependency> <groupId>org.passay</groupId> <artifactId>passay</artifactId> <version>${passay.version}</version> </dependency> <dependency> <groupId>org.springframework.mobile</groupId> <artifactId>spring-mobile-device</artifactId> <version>1.1.4.RELEASE</version> </dependency> <!--JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency> <dependency> <groupId>az.com.cybernet.utilities</groupId> <artifactId>utilities</artifactId> <version>0.0.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>az.com.cybernet.beans</groupId> <artifactId>disieibeans</artifactId> <version>0.0.1</version> <scope>compile</scope> </dependency> <!-- DB dependencies --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.4-1200-jdbc41</version> </dependency> <dependency> <groupId>javax.el</groupId> <artifactId>el-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>4.8</version> </dependency> <!-- REDIS --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>2.0.0.BUILD-SNAPSHOT</version> <type>pom</type> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.5.1</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>4.2.0.RELEASE</version> </dependency> </dependencies>
没有Redis,一切都会很好,但是有了Redis,我将无法获得当前经过身份验证的用户的主体
我只是将jedis客户端更改为生菜客户端,如下所示,它解决了问题:
<dependency> <groupId>biz.paluch.redis</groupId> <artifactId>lettuce</artifactId> <version>3.5.0.Final</version> </dependency>
在Spring安全之前,Principal对象为null,因为Jedis客户端尚未创建名为“ JSESSIONID”的cookie。
@EnableRedisHttpSession public class RedisConfig { @Bean public LettuceConnectionFactory connectionFactory() { return new LettuceConnectionFactory(); } @Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer = new DefaultCookieSerializer(); serializer.setCookieName("JSESSIONID"); // <1> serializer.setCookiePath("/"); // <2> serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); // <3> return serializer; } }