我们希望自己的微服务能够在用户登录之后才可以访问,而单独给每个微服务单独做用户权限模块就显得很弱了,从复用角度来说是需要重构的,从功能角度来说,也是欠缺的。尤其是前后端完全分离之后,我们的用户信息不一定存在于 Session 会话中,本节内容使用OAuth2+JWT的功能恰好能够弥补这块。
应用场景
常见的应用场景如下图,用户通过浏览器进行登录,一旦确定用户名和密码正确,那么在服务器端使用秘钥创建 JWT,并且返回给浏览器;接下来我们的请求需要在头部增加 jwt 信息,服务器端进行解密获取用户信息,然后进行其他业务逻辑处理,再返回客户端
image
实战案例
我们基于 Spring Cloud 的骨架进行搭建,分为3个工程,eureka 服务器,负责微服务注册;auth 服务器,负责授权,需要提供 clientId 和密码;user 微服务,一个微服务提供,他作为资源服务器,资源是被保护起来的,需要相应的权限才能访问。User 微服务得到用户请求的 JWT 之后,使用公钥解密,得到用户信息和权限信息。
image
编写主 maven 工程
构建一个 maven 项目,打包类型是 pom,其中该 pom 文件内容如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.cnsesan</groupId> <artifactId>cnsean-architecture-spring-cloud</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>pom</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.13.RELEASE</version> <relativePath/> </parent> <properties> <security.version>1.0.0-SNAPSHOT</security.version> <java.version>1.8</java.version> </properties> <!-- 替我们管理依赖的版本信息 --> <dependencyManagement> <dependencies> <!-- spring io --> <dependency> <groupId>io.spring.platform</groupId> <artifactId>platform-bom</artifactId> <version>Brussels-SR11</version> <type>pom</type> <scope>import</scope> </dependency> <!-- spring cloud --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <compilerVersion>1.8</compilerVersion> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> <modules> <module>cnsesan-eureka-single</module> <module>cnsesan-uaa-service</module> <module>cnsesan-user-service</module> </modules></project>
上述的版本是经过测试可以正常使用的,如果需要更新到 SpringBoot2.0版本,需要更新其他版本进行对应。同时也看到该 pom 内部包含3个 module,接下来我们分别来构建这3个 module。
构建 EurekaServer
这里我们构建的是单个 Eureka 服务器作为测试,真实环境是需要集群的。在父项目的基础上,右键构建,如下图(IDE 为 STS)
image
image
配置 pom,加入依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.cnsesan</groupId> <artifactId>cnsean-architecture-spring-cloud</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>cnsesan-eureka-single</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> </dependencies></project>
这里仅仅引入 eureka 服务器端的依赖即可
配置 yml 文件
spring: application: name: eureka-server eureka: instance: hostname: localhost client: serviceUrl: defaultZone: http://localhost:8762/eureka/ register-with-eureka: false fetch-registry: false# instance: # preferIpAddress: true server: # 关闭自我保护模式(缺省为打开) enable-self-preservation: false # 续期时间,即扫描失效服务的间隔时间(缺省为60*1000ms) eviction-interval-timer-in-ms: 5000logging: level: com.netflix: INFO server: port: 8762
端口是8762,名称是eureka-server
在 Application 启动类中添加注解
package com.cnsesan.eureka;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.builder.SpringApplicationBuilder;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@EnableEurekaServer@SpringBootApplicationpublic class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class) .web(true).run(args); } }
找到BootDashboard,运行eureka
image
构建 Uaa 授权服务
同样构建 maven 项目,导入依赖,pom 文件为
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.cnsesan</groupId> <artifactId>cnsean-architecture-spring-cloud</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>cnsesan-uaa-service</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <configuration> <nonFilteredFileExtensions>cert</nonFilteredFileExtensions> <nonFilteredFileExtensions>jks</nonFilteredFileExtensions> </configuration> </plugin> </plugins> </build> </project>
其中最后一段是防止打包的时候把公钥和私钥文件搞乱,读取不了。
接下来配置 application.yml
spring: application: name: uaa-service datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 username: root password: root jpa: hibernate: ddl-auto: update show-sql: trueserver: port: 9999eureka: client: serviceUrl: defaultZone: http://localhost:8762/eureka/
端口是9999,服务名称是 uaa-service
与 application.yml 相同地方还需要2个文件,分别是cnsesan-jwt.jks和 public.cert
我们先把这两个文件弄出来
keytool -genkeypair -alias cnsesan-jwt -validity 3650 -keyalg RSA -dname "CN=jwt,OU=cnsesan,O=cnsesan,L=zurich,S=zurich,C=CH" -keypass cnsesan123 -keystore cnsesan-jwt.jks -storepass cnsesan123
如上操作得到cnsesan-jwt.jks
然后需要的都公钥文件,如下
keytool -list -rfc --keystore cnsesan-jwt.jks | openssl x509 -inform pem -pubkey
输入密码 cnsesan123,将如下片段拷贝到新文件public.cert
image
可以得到public.cert
将这两个文件拷贝到 resource 目录下
image
接下来首先编写启动类,主要是几个注解
package com.cnsesan.uaa;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.builder.SpringApplicationBuilder;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;@SpringBootApplication@EnableResourceServer@EnableEurekaClientpublic class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class) .web(true).run(args); } }
然后是编写我们的配置类,也是最核心的地方
首先编写配置Spring Security
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired private UserServiceDetail userServiceDetail ; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .exceptionHandling() .authenticationEntryPoint((request,response,authException)->response.sendError(HttpServletResponse.SC_UNAUTHORIZED)) .and() .authorizeRequests() .antMatchers("/**").authenticated() .and() .httpBasic() ; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userServiceDetail).passwordEncoder(new BCryptPasswordEncoder()); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
这里有个UserServiceDetail
,实现了UserDetailsService,他的代码如下,主要是负责用户信息获取的
@Servicepublic class UserServiceDetail implements UserDetailsService { @Autowired private UserDao userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userRepository.findByUsername(username); } }
UserDao 类 是一个接口,使用 JPA 的方式,如下
public interface UserDao extends JpaRepository<User, Long>{ User findByUsername(String username); }
User 和 Role 两个实体类需要做如下的实现
@Entitypublic class User implements UserDetails, Serializable{ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; @Column(nullable=false,unique=true) private String username; @Column() private String password; @ManyToMany(cascade = CascadeType.ALL, fetch= FetchType.EAGER) @JoinTable(name="user_role",joinColumns=@JoinColumn(name="user_id",referencedColumnName="id"),inverseJoinColumns=@JoinColumn(name="role_id",referencedColumnName="id")) private List<Role> authorities; public User() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setAuthorities(List<Role> authorities) { this.authorities = authorities; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
@Entitypublic class Role implements GrantedAuthority{ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; @Column(nullable=false) private String name; @Override public String getAuthority() { return name; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
其次编写OAuth2Config
,该类是配置OAuth2相关内容
@Configuration@EnableAuthorizationServer // 开启授权服务功能public class OAuth2Config extends AuthorizationServerConfigurerAdapter { @Autowired @Qualifier("authenticationManagerBean") private AuthenticationManager authenticationManager; // 配置客户端基本信息 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient("user-service")// 创建一个客户端 名字是user-service .secret("123456") .scopes("service") .authorizedGrantTypes("refresh_token", "password") .accessTokenValiditySeconds(3600); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtTokenEnhancer()) .authenticationManager(authenticationManager); } public TokenStore tokenStore() { return new JwtTokenStore(jwtTokenEnhancer()); } private JwtAccessTokenConverter jwtTokenEnhancer() { KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("cnsesan-jwt.jks"), "cnsesan123".toCharArray()); JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setKeyPair(keyStoreKeyFactory.getKeyPair("cnsesan-jwt")); return converter; } }
到此为止,授权服务器搭建完毕,启动,
在测试之前,数据库需要增加一些表
DROP TABLE IF EXISTS `clientdetails`;CREATE TABLE `clientdetails` ( `appId` varchar(128) NOT NULL, `resourceIds` varchar(256) DEFAULT NULL, `appSecret` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `grantTypes` varchar(256) DEFAULT NULL, `redirectUrl` varchar(256) DEFAULT NULL, `authorities` varchar(256) DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additionalInformation` varchar(4096) DEFAULT NULL, `autoApproveScopes` varchar(256) DEFAULT NULL, PRIMARY KEY (`appId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;;-- ------------------------------ Table structure for oauth_access_token-- ----------------------------DROP TABLE IF EXISTS `oauth_access_token`;CREATE TABLE `oauth_access_token` ( `token_id` varchar(256) DEFAULT NULL, `token` blob, `authentication_id` varchar(128) NOT NULL, `user_name` varchar(256) DEFAULT NULL, `client_id` varchar(256) DEFAULT NULL, `authentication` blob, `refresh_token` varchar(256) DEFAULT NULL, PRIMARY KEY (`authentication_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;;-- ------------------------------ Table structure for oauth_approvals-- ----------------------------DROP TABLE IF EXISTS `oauth_approvals`;CREATE TABLE `oauth_approvals` ( `userId` varchar(256) DEFAULT NULL, `clientId` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `status` varchar(10) DEFAULT NULL, `expiresAt` datetime DEFAULT NULL, `lastModifiedAt` datetime DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;;-- ------------------------------ Table structure for oauth_client_details-- ----------------------------DROP TABLE IF EXISTS `oauth_client_details`;CREATE TABLE `oauth_client_details` ( `client_id` varchar(128) NOT NULL, `resource_ids` varchar(256) DEFAULT NULL, `client_secret` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `authorized_grant_types` varchar(256) DEFAULT NULL, `web_server_redirect_uri` varchar(256) DEFAULT NULL, `authorities` varchar(256) DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additional_information` varchar(4096) DEFAULT NULL, `autoapprove` varchar(256) DEFAULT NULL, PRIMARY KEY (`client_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;;-- ------------------------------ Table structure for oauth_client_token-- ----------------------------DROP TABLE IF EXISTS `oauth_client_token`;CREATE TABLE `oauth_client_token` ( `token_id` varchar(256) DEFAULT NULL, `token` blob, `authentication_id` varchar(128) NOT NULL, `user_name` varchar(256) DEFAULT NULL, `client_id` varchar(256) DEFAULT NULL, PRIMARY KEY (`authentication_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;;-- ------------------------------ Table structure for oauth_code-- ----------------------------DROP TABLE IF EXISTS `oauth_code`;CREATE TABLE `oauth_code` ( `code` varchar(256) DEFAULT NULL, `authentication` blob) ENGINE=InnoDB DEFAULT CHARSET=utf8;;-- ------------------------------ Table structure for oauth_refresh_token-- ----------------------------DROP TABLE IF EXISTS `oauth_refresh_token`;CREATE TABLE `oauth_refresh_token` ( `token_id` varchar(256) DEFAULT NULL, `token` blob, `authentication` blob) ENGINE=InnoDB DEFAULT CHARSET=utf8;;SET FOREIGN_KEY_CHECKS = 1;
现在可以测试
curl user-service:123456@localhost:9999/oauth/token -d grant_type=password -d username=ts -d password=123456
得到如下
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MzA4NTEyNjMsInVzZXJfbmFtZSI6InRzIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIiwiUk9MRV9BRE1JTiIsIkFETUlOIl0sImp0aSI6ImNkYmE1MTExLTlmNmEtNGU1NS04ZmRhLTUzYzAzOWYxOWRiMiIsImNsaWVudF9pZCI6InVzZXItc2VydmljZSIsInNjb3BlIjpbInNlcnZpY2UiXX0.KFO-37xi0z086lbdOzRKNZBijDVSi4dlpdFVzhHvXkvbypsEGLIrurntWf5UhQaFZ9xB8JPGIgjvbybfrpZxWwTJgX04NpXSkrATBsQucI-J181lhuHeefwLDfPsAIRP4QGbzbgLZ_4RrAdi66PU2oKIYV0-REUIhtRNzJhUFCZckWpa2pLo0hwzq8gzBVFoOrsWtwTeDrGKc3F7RWCsDJeByGvyBfI33n6r3S6XOSt0aNvLBrihqBAqPgudWeCHO-4gQ5MBh7SCz9H-oO92vviNaiEVklEJP24l52R0TTFsxky4YbUsozPU6YXyoxa5o2dxJo_pWoek-GmdW7_YJw","token_type":"bearer","refresh_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ0cyIsInNjb3BlIjpbInNlcnZpY2UiXSwiYXRpIjoiY2RiYTUxMTEtOWY2YS00ZTU1LThmZGEtNTNjMDM5ZjE5ZGIyIiwiZXhwIjoxNTMzNDM5NjYzLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiLCJST0xFX0FETUlOIiwiQURNSU4iXSwianRpIjoiYzAxMGY4MmUtZDRkYS00MTNmLWEwMTctZTM0MzA2YWY2OWViIiwiY2xpZW50X2lkIjoidXNlci1zZXJ2aWNlIn0.RKe3rjgrl3Hu1jAVa68csSJ-Y2b75LWYgke5urscQGv2OH7dOuOmcyUo9K_dfvT9Jz9WNDdz-rmdCBfw7bPdoDfCh4wCi-2Xh0ufl6Q4RO6eWLGSpcA2x7-dJsh325Ylje6PC3-__ID_SS1znM4zw_xBubp1Uah0hpuEkqtKUgPWOnV4eybvGvJlSqbZLhenCQrhYCrWW781jYkCKm8E6AoQHUyVRrQ_jiyfcfYQs9wEuJNtuZXwoYIW4xM-hDr1rVkPab8thjZ3EkVnIgoTXo0t_i_SiVWCrNo2874QZq8BBj3-St7YyW_JyQM0jGT5VrgkcbCiuCZebDdyIBBAdQ","expires_in":3599,"scope":"service","jti":"cdba5111-9f6a-4e55-8fda-53c039f19db2"}%
构建 user 微服务
同样构建 maven module,名称是 cnsesan-user-service
pom 依赖和上面的 uaa 类似,多了如下2个依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency>
配置文件 application.yml
server: port: 9090 eureka: client: serviceUrl: defaultZone: http://localhost:8762/eureka/ spring: application: name: user-service datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 username: root password: root jpa: hibernate: ddl-auto: update show-sql: true feign: hystrix: enabled: true
同时把 public.cert拷贝一份到 resource 目录
接下来还是先编写启动类
@EnableFeignClients@SpringBootApplication@EnableEurekaClientpublic class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class) .web(true).run(args); } }
配置资源服务器
@Configuration @EnableResourceServerpublic class ResourceServerConfig extends ResourceServerConfigurerAdapter{ @Autowired TokenStore tokenStore ; @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/user/login","/user/register").permitAll() .antMatchers("/**").authenticated(); } @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.tokenStore(tokenStore); } }
配置 JWT
@Configurationpublic class JwtConfig { @Autowired JwtAccessTokenConverter jwtAccessTokenConverter; @Bean @Qualifier("tokenStore") public TokenStore tokenStore(){ return new JwtTokenStore(jwtAccessTokenConverter); } @Bean public JwtAccessTokenConverter jwtTokenEnhancer(){ JwtAccessTokenConverter converter= new JwtAccessTokenConverter (); Resource resource= new ClassPathResource ("public.cert"); String publicKey; try { publicKey=new String(FileCopyUtils.copyToByteArray(resource.getInputStream())); } catch (IOException e) { throw new RuntimeException(); } converter.setVerifierKey(publicKey); return converter; } }
配置 开启方法级别安全验证
@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true) //开启方法级别安全验证public class GlobalMethodSecurityConfig { }
编写用户相关服务,用户注册和用户登录
@Servicepublic class UserServiceDetail { @Autowired private UserDao userRepository; @Autowired AuthServiceClient client; public User insertUser(String username,String password){ User user=new User(); user.setUsername(username); user.setPassword(BPwdEncoderUtil.BCryptPassword(password)); return userRepository.save(user); } public UserLoginDTO login(String username,String password){ User user=userRepository.findUserByUsername(username); if(user==null){ throw new RuntimeException("用户不存在"); } if(!BPwdEncoderUtil.matches(password, user.getPassword())){ throw new RuntimeException("用户密码不对"); } //dXNlci1zZXJ2aWNlOjEyMzQ1Ng== 是 user-service:123456的 base64编码 JWT jwt=client.getToken("Basic dXNlci1zZXJ2aWNlOjEyMzQ1Ng==", "password", username, password); if(jwt==null){ throw new RuntimeException("用户Token有问题"); } UserLoginDTO dto=new UserLoginDTO(); dto.setUser(user); dto.setJwt(jwt); return dto; } }
上面服务有个AuthServiceClient类,他是个接口,使用Feign向 uaa 去请求,同时加以熔断机制进行处理
@FeignClient(value="uaa-service", fallback =AuthServiceHystrix.class )public interface AuthServiceClient { @PostMapping(value ="/oauth/token") JWT getToken(@RequestHeader(value="Authorization")String authorization, @RequestParam("grant_type")String type, @RequestParam("username")String username, @RequestParam("password")String password); }
而AuthServiceHystrix是一个默认的处理方式
@Componentpublic class AuthServiceHystrix implements AuthServiceClient{ @Override public JWT getToken(String authorization, String type, String username, String password) { // TODO Auto-generated method stub return null; } }
JWT 是一个 POJO 类
public class JWT { private String access_token,token_type,refresh_token,scope,jti; private int expires_in; //set和 get}
UserDao,User,Role和之前的 uaa 项目一样,不在赘述。
针对异常做统一处理
@ControllerAdvice@ResponseBodypublic class ExceptionHandle { @ExceptionHandler(RuntimeException.class) public ResponseEntity<String> handleException(Exception e){ return new ResponseEntity (e.getMessage () , HttpStatus.OK) ; } }
编写我们的控制层的类
@RestController@RequestMapping("/user")public class UserController { @Autowired UserServiceDetail userServiceDetail; @PostMapping("/register") public User postUser(@RequestParam("username")String username,@RequestParam("password")String password){ return userServiceDetail.insertUser(username, password); } @PostMapping ("/login") public UserLoginDTO login(@RequestParam ("username")String username,@RequestParam ("password")String password){ return userServiceDetail.login(username, password); } }
其中涉及的一个工具类BPwdEncoderUtil
public class BPwdEncoderUtil { private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder (); public static String BCryptPassword(String password){ return encoder.encode(password); } public static boolean matches (CharSequence rawPassword, String encodedPassword) { return encoder.matches(rawPassword, encodedPassword); } }
到此为止用户服务编写完毕,我们开始测试,打开 postman 工具
先注册一个用户
http://localhost:9090/user/register?username=shun&password=123456
image
使用 Post 方式,输入http://localhost:9090/user/login?username=shun&password=123456,首先需要数据库有这样的数据
可以看到
image
之后的访问需要带上我们的 Token 令牌
编写个测试 Controller
@RestControllerpublic class DemoController { @RequestMapping("/hi") public String hi(){ return "hi,你好"; } @RequestMapping("/hello") @PreAuthorize("hasAuthority('ROLE_ADMIN')") public String hello(){ return "hello,你好"; } @RequestMapping("/getPrincipal") public OAuth2Authentication getPrinciple(OAuth2Authentication oauth2Authentication,Principal principal,Authentication authentication){ System.out.println("===================================="); System.out.println(oauth2Authentication); System.out.println(principal); System.out.println(authentication); System.out.println("===================================="); return oauth2Authentication; } }
我们直接访问 http://localhost:9090/hi
image
我们需要在请求头增加 Token
image
这样才可以正常访问
但是如果需要 admin 权限的,即使带上也是访问不了的
我们可以测试http://localhost:9090/hello,这个接口需要 ROLE_ADMIN 权限
image
我们切换另外一个用户
image
image
作者:breezedancer
链接:https://www.jianshu.com/p/24764aba1012
热门评论
想问一下,我这边获取到了access_token了,携带token过去访问资源服务的时候返回
{
"error": "invalid_token",
"error_description": "Cannot convert access token to JSON"
}
这个可能是什么原因呢
想问一下,我这边获取到了access_token了,携带token过去访问资源服务的时候返回
{
"error": "invalid_token",
"error_description": "Cannot convert access token to JSON"
}
这个可能是什么原因呢
源码能共享么?