Jwt 全称JSON Web Token,是无状态登录的一种实现方式。什么是无状态,就是服务端无状态,即无session。用户的登录状态由客户端保存,每次请求携带token,然后解析。多了不说,想了解更多的可以看https://www.jianshu.com/p/576dbf44b2ae,这里介绍的很详细
先看看项目结构:
├─auth-server │ ├─src │ │ ├─main │ ├─java │ │ └─com │ │ └─lhf │ │ └─authserver │ │ │ AuthServerApplication.java │ │ │ │ │ └─config │ │ AuthServerConfig.java │ │ SecurityConfig.java │ │ │ └─resources │ │ application.properties │ │ │ ├─static │ └─templates │ ├─resource-server │ ├─src │ │ ├─main │ ├─java │ │ └─com │ │ └─lhf │ │ └─resourceserver │ │ │ ResourceServerApplication.java │ │ │ │ │ ├─config │ │ │ ResourceServerConfig.java │ │ │ │ │ └─controller │ │ TestController.java │ │ │ └─resources │ │ application.yml │ │ │ ├─static │ └─templates └─test ├─src │ ├─main ├─java │ └─com │ └─lhf │ └─test │ │ TestApplication.java │ │ │ └─controller │ TestController.java │ └─resources │ application.yml │ ├─static └─templates index.html
可以看到,和上篇文章的结构一样。
一、搭建授权服务器
添加maven节点
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency>
添加SecurityConfig.java
package com.lhf.authserver.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * <p></p> * * @author zy 刘会发 * @version 1.0 * @since 2020/5/11 */ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 密码比对器 * @return */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 用户配置 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("admin").password(passwordEncoder().encode("123")).roles("admin"); } /** * 安全配置,这里只是开启表单登录,不做其他配置,将交给资源服务器配置 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().formLogin(); } }
添加AuthServerConfig.java
package com.lhf.authserver.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenEnhancerChain; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import javax.annotation.Resource; import java.util.Arrays; /** * <p></p> * * @author zy 刘会发 * @version 1.0 * @since 2020/5/11 */ @Configuration @EnableAuthorizationServer public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { @Resource private ClientDetailsService clientDetailsService; @Autowired private PasswordEncoder passwordEncoder; /** * 授权服务器安全配置 * @param security * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.checkTokenAccess("permitAll()").allowFormAuthenticationForClients(); } /** * 授权服务客户端配置 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("novel") .secret(passwordEncoder.encode("123")) .autoApprove(true) .resourceIds("lhf") .scopes("all") .redirectUris("http://localhost:8082/index.html") .authorizedGrantTypes("authorization_code"); } /** * 授权服务器节点配置 * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenServices(defaultTokenServices()); } @Bean public DefaultTokenServices defaultTokenServices() { DefaultTokenServices bean = new DefaultTokenServices(); bean.setTokenStore(jwtTokenStore()); bean.setSupportRefreshToken(true); bean.setAccessTokenValiditySeconds(60 * 60); bean.setRefreshTokenValiditySeconds(30 * 60); bean.setClientDetailsService(clientDetailsService); TokenEnhancerChain chain = new TokenEnhancerChain(); chain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter()));//将token的增强方式注入进去 bean.setTokenEnhancer(chain); return bean; } /** * token保存方式使用jwt * @return */ @Bean public JwtTokenStore jwtTokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } /** * jwt配置 * @return */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter bean = new JwtAccessTokenConverter(); bean.setSigningKey("zjs"); return bean; } }
和普通的方式没多大的差别,只是TokenStore的实现方式不同
二、搭建资源服务
maven节点和授权服务一样
package com.lhf.resourceserver.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; /** * <p></p> * * @author zy 刘会发 * @version 1.0 * @since 2020/5/11 */ @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Bean public RemoteTokenServices remoteTokenServices() { RemoteTokenServices bean = new RemoteTokenServices(); bean.setClientId("novel"); bean.setClientSecret("123"); bean.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token"); return bean; } @Bean public JwtTokenStore jwtTokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter bean = new JwtAccessTokenConverter(); bean.setSigningKey("zjs"); return bean; } @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.tokenServices(remoteTokenServices()).resourceId("lhf").tokenStore(jwtTokenStore()); } @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated(); } }
同样的,资源服务器也得添加token的实现了,因为不再是默认的配置了。
添加测试接口
package com.lhf.resourceserver.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * <p></p> * * @author zy 刘会发 * @version 1.0 * @since 2020/5/11 */ @RestController public class TestController { @GetMapping("hello") public String hello() { return "hello"; } }
三、测试服务
添加maven节点
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
添加登录页面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>第三方登录</title> </head> <body> <a href="http://localhost:8080/oauth/authorize?client_id=novel&response_type=code&scope=all&redirect_uri=http://localhost:8082/index.html">点击登录</a> <h2 th:text="${msg}"></h2> </body> </html>
添加控制层
package com.lhf.test.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.ModelAndView; import java.util.Map; /** * <p></p> * * @author zy 刘会发 * @version 1.0 * @since 2020/5/11 */ @Controller public class TestController { @Autowired private RestTemplate restTemplate; @GetMapping("index.html") public Object index(ModelAndView mv, String code) { mv.setViewName("index"); if (code != null) { MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); map.add("code", code); map.add("client_id", "novel"); map.add("client_secret", "123"); map.add("redirect_uri", "http://localhost:8082/index.html"); map.add("grant_type", "authorization_code"); Map<String, String> resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class); String access_token = resp.get("access_token"); System.out.println(access_token); HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "Bearer " + access_token); HttpEntity<Object> httpEntity = new HttpEntity<>(headers); ResponseEntity<String> entity = restTemplate.exchange("http://localhost:8081/hello", HttpMethod.GET, httpEntity, String.class); mv.addObject("msg", entity.getBody()); } return mv; } }
测试:
请求 http://localhost:8082/index.html
点击登录
输入用户名密码,登录
成功请求到资源服务
项目源码地址:
gitee: https://gitee.com/lhfas/security-jwt.git
github: https://github.com/Liuhuifa/security-jwt.git