关于Oath2.0
oauth2.0主要的两方面的工作:
- 进行认证===登录 授权(CRUD权限)—security
- 授权—给第三方应用办法token—说明第三方应用已经得到了我认证了,之后只要携带我们给它颁发的token就可以访问微服务集群中的服务了
四种授权模式
code码模式=====authorization_code
步骤:
- 认证
- 申请服务端,申请颁发code码
- 申请服务端,申请颁发token
- 携带token访问资源服务器
思路:当用户同意授权后,授权服务端会先向第三方返回code码,第三方再携带code码来申请token,授权服务端最后再返回token。第三方再携带token来访问资源服务器中的资源
简化模式=====implicit
步骤:
- 认证
- 申请服务端,申请颁发token
- 携带token访问资源服务器
思路:当第三方发出请求申请颁发token,授权服务端返回token。第三方再携带token来访问资源服务器中的资源
密码模式=====password
步骤:
- 携带资源拥有者的账号和密码直接申请服务端,申请颁发token
- 携带token访问资源服务器
客户端模式======client_credentials
步骤:
- 直接向服务端发出申请,申请颁发token
- 携带token访问资源服务器
说明:code码模式和简单模式都需要显现的进行认证登录,而密码模式不需要显现的认证登录,但是也作为认证登录,只不过是隐藏性进行认证登录。
授权服务器(内存获取登录用户)
第一步:
pom文件;因为颁发的token需要保存,所以在这里使用redis来对颁发的token进行定时销毁和保存;Oauth2.0+Redis+Web
<!--Oauth2.0的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <!--redis的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--手动引入redis连接池的依赖--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
第二步:
配置文件:配置redis的相关配置
#授权中心的服务名称 spring.application.name=auth-server #授权中心服务的端口 -- 一般都是9999 server.port=9999 #--------------------redis的配置--------------------- #redis服务器的ip(安装redis的linux的ip) spring.redis.host=*** #redis的端口 spring.redis.port=*** #如果redis设置了密码则指定密码 spring.redis.password=*** #操作redis的数据库的下标 spring.redis.database=*** #redis连接池的配置 #设置池中jedis对象的个数 spring.redis.lettuce.pool.max-active=10 #设置池中jedis对象的最大空闲个数 spring.redis.lettuce.pool.max-idle=10 #设置池中jedis对象的最小空闲个数 spring.redis.lettuce.pool.min-idle=3
第三步:
主启动类,添加注解**@EnableAuthorizationServer**
//开启授权服务器 @EnableAuthorizationServer
第四步:
创建授权服务器的配置类,实现AuthorizationServerConfigurerAdapter类,并标记注解**@Configuration**
@Configuration public class OauthServerConfig extends AuthorizationServerConfigurerAdapter { }
配置三个属性:
指定token的存储方式 – 这里选择redis。
//注入redis连接工厂,在项目启动时候会自动注入,不需要自己手动使用@Bean来注入 @Autowired private RedisConnectionFactory redisConnectionFactory; /* 指定token的存储方式: 向容器中加入TokenStore的bean对象;因为是先存到redis,所以是RedisTokenStore;如果是使用JWT来存储的话,则返回JwtTokenStore对象 */ @Bean public TokenStore tokenStore(){ return new RedisTokenStore(redisConnectionFactory); }
配置第三方应用 – 只有配置的第三方应用才可向授权服务器申请token。
!!!!!!内存保存第三方情况下,一般用于第三方较少 /** * 注入加密器 */ @Autowired private PasswordEncoder passwordEncoder; /** *重写方法参数为ClientDetailsServiceConfigurer对象的方法,配置第三方应用 */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //第三方较少时,一般就在内存中配置 clients.inMemory() //配置第三方id(字符串标识,随便给只要不重复) .withClient("baidu") //配置第三方密码 -- 通过id和密码来匹配第三方 .secret(passwordEncoder.encode("baidu-secret")) //作用域(没有实质意义,只是个提示,可以随便给) .scopes("all") //授权模式--code码模式 //如果要修改为其余模式{简化模式===implicit;密码模式===password;客户端模式===client_credentials;code模式===authorization_code} .authorizedGrantTypes("authorization_code") //token的过期时间--两小时,单位秒 .accessTokenValiditySeconds(7200) //授权成功后重定向的url地址(必须是https协议,且是公网地址[百度 新浪...]) .redirectUris("https://www.baidu.com"); }
//关于加密器:因为在存储密码的时候,一般不会直接存储显视密码,而是使用加密字符串来保存 //注入加密器 @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }
向资源服务器暴露token的存储方式。
//注入认证管理器,适用于password模式加客户端模式 @Autowired private AuthenticationManager authenticationManager;
/** * 重写方法参数为AuthorizationServerEndpointsConfigurer对象的方法 * 这个方法用来配置向资源服务器暴露token的存储方式 */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //redis的TokenStore endpoints.tokenStore(tokenStore()); //给授权服务器暴露认证处理器,适用于password模式加客户端模式 endpoints.authenticationManager(authenticationManager); }
第五步:
创建Spring Security的配置类;用来配置登录页面的账号+密码。还有请求的处理
//Security的配置类 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { //注入解密器 @Autowired private PasswordEncoder passwordEncoder; //进行认证 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //在内存中配置用户 auth.inMemoryAuthentication() .withUser("admin")//用户名 .password(passwordEncoder.encode("123"))//密码 .authorities("user:all");//权限 } //处理请求 @Override protected void configure(HttpSecurity http) throws Exception { //提供登录表单 http.formLogin(); //所有的资源请求必须通过认证之后才可以访问 http.authorizeRequests().anyRequest().authenticated(); } }
//!!!向IOC容器中加入认证管理器--不需要表单让用户可隐式登录;适用于密码模式和客户端授权模式,如果只是code模式和简单模式可以不需要添加 @Bean @Override public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); }
授权服务器(数据库保存用户)
第一步:
pom文件;因为颁发的token需要保存,所以在这里使用redis来对颁发的token进行定时销毁和保存;Oauth2.0+Redis+Web;同时查询用户从数据库进行查询,所以需要引入MySQL+Mybatis的依赖
<!--Oauth2.0的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <!--redis的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--手动引入redis连接池的依赖--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!--引入mybatis依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <!--引入mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
第二步:
配置文件:配置redis的相关配置
#授权中心的服务名称 spring.application.name=auth-server #授权中心服务的端口 -- 一般都是9999 server.port=9999 #--------------------redis的配置--------------------- #redis服务器的ip(安装redis的linux的ip) spring.redis.host=localhost #redis的端口 spring.redis.port=6379 #如果redis设置了密码则指定密码 #spring.redis.password=*** #操作redis的数据库的下标 spring.redis.database=1 #redis连接池的配置 #设置池中jedis对象的个数 spring.redis.lettuce.pool.max-active=10 #设置池中jedis对象的最大空闲个数 spring.redis.lettuce.pool.max-idle=10 #设置池中jedis对象的最小空闲个数 spring.redis.lettuce.pool.min-idle=3 #mysql驱动类 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #url spring.datasource.url=jdbc:mysql://localhost:3306/work?serverTimezone=UTC #mysql的账号 spring.datasource.username=root #mysql的密码 spring.datasource.password=000730 #指定sql映射文件的位置 mybatis.mapper-locations=classpath:mapper/*.xml #开启驼峰命名规则 mybatis.configuration.map-underscore-to-camel-case=true #开启日志输出 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
第三步:
主启动类,添加注解**@EnableAuthorizationServer和@MapperScan**
//开启授权服务器 @EnableAuthorizationServer 扫描器(指定Mapper接口所在的位置) @MapperScan(basePackages = {"com.xa.mapper"})
第四步:
创建授权服务器的配置类,实现AuthorizationServerConfigurerAdapter类,并标记注解**@Configuration**
@Configuration public class OauthServerConfig extends AuthorizationServerConfigurerAdapter { }
配置三个属性:
指定token的存储方式 – 这里选择redis。
//注入redis连接工厂,在项目启动时候会自动注入,不需要自己手动使用@Bean来注入 @Autowired private RedisConnectionFactory redisConnectionFactory; /* 指定token的存储方式: 向容器中加入TokenStore的bean对象;因为是先存到redis,所以是RedisTokenStore;如果是使用JWT来存储的话,则返回JwtTokenStore对象 */ @Bean public TokenStore tokenStore(){ return new RedisTokenStore(redisConnectionFactory); }
配置第三方应用 – 只有配置的第三方应用才可向授权服务器申请token。
!!!!!!内存保存第三方情况下,一般用于第三方较少 /** * 注入加密器 */ @Autowired private PasswordEncoder passwordEncoder; /** *重写方法参数为ClientDetailsServiceConfigurer对象的方法,配置第三方应用 */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //第三方较少时,一般就在内存中配置 clients.inMemory() //配置第三方id(字符串标识,随便给只要不重复) .withClient("baidu") //配置第三方密码 -- 通过id和密码来匹配第三方 .secret(passwordEncoder.encode("baidu-secret")) //作用域(没有实质意义,只是个提示,可以随便给) .scopes("all") //授权模式--code码模式 //如果要修改为其余模式{简化模式===implicit;密码模式===password;客户端模式===client_credentials;code模式===authorization_code} .authorizedGrantTypes("authorization_code") //token的过期时间--两小时,单位秒 .accessTokenValiditySeconds(7200) //授权成功后重定向的url地址(必须是https协议,且是公网地址[百度 新浪...]) .redirectUris("https://www.baidu.com"); }
//关于加密器:因为在存储密码的时候,一般不会直接存储显视密码,而是使用加密字符串来保存 //注入加密器 @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }
向资源服务器暴露token的存储方式。
//注入认证管理器,适用于password模式加客户端模式 @Autowired private AuthenticationManager authenticationManager;
/** * 重写方法参数为AuthorizationServerEndpointsConfigurer对象的方法 * 这个方法用来配置向资源服务器暴露token的存储方式 */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //redis的TokenStore endpoints.tokenStore(tokenStore()); //给授权服务器暴露认证处理器,适用于password模式加客户端模式 endpoints.authenticationManager(authenticationManager); }
1
第五步:
创建用户实体类,实现UserDetails接口,并重写方法
/** * 用户状态字段(1:正常 0:禁用) */ private Integer status; /** * 用户的权限合集 */ private List<String> auths; /** * 返回权限合集 */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { //先判断权限合集是否为空,或无权限 if (CollectionUtils.isEmpty(auths)) { return Collections.emptyList(); } List<GrantedAuthority> authorities = new ArrayList<>(); for (String auth : auths) { //将字符串形式的权限转成GrantedAuthority对象的权限 SimpleGrantedAuthority sga = new SimpleGrantedAuthority(auth); authorities.add(sga); } return authorities; } /** * @return 用户名从数据库中查询的对应的密码 */ @Override public String getPassword() { return upwd; } /** * @return 登录的用户名 */ @Override public String getUsername() { return uname; } /** * 指定账号是否过期 */ @Override public boolean isAccountNonExpired() { return status == 1; } /** * 指定账号是否被冻结 */ @Override public boolean isAccountNonLocked() { return status == 1; } /** * 指定密码是否过期 */ @Override public boolean isCredentialsNonExpired() { return status == 1; } /** * 指定账号是否可用 */ @Override public boolean isEnabled() { return status == 1; }
第六步:
创建service类,实现UserDetailsService接口,重写**loadUserByUsername(String username)**方法
//注入用户表的mapper接口 @Autowired private SysUserMapper sysUserMapper; //注入权限表的mapper接口 @Autowired private SysPermissionMapper sysPermissionMapper; /** * 处理登录的用户是否存在 * @param s 登录表单的用户名 * @return 继承了UserDetails类的user实体类 */ @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { //根据用户名查询数据库,查询得到一个用户对象 SysUser sysUser = sysUserMapper.selectByUserName(s); //获取用户对象的uid Integer uid = sysUser.getUid(); //使用uid来查询权限合集 List<String> auths=sysPermissionMapper.selectPermissionByUserId(uid); sysUser.setAuths(auths); return sysUser; }
第七步:
创建Spring Security的配置类;用来配置登录页面账号密码从数据库来查询。还有请求的处理
//Security的配置类 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { //注入自定义的实现了UserDetailsService接口的实现类 @Autowired private MyUserDetailsService myUserDetailsService; //进行认证 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /* 从数据库中查询用户, */ auth.userDetailsService(myUserDetailsService); } //处理请求 @Override protected void configure(HttpSecurity http) throws Exception { //提供登录表单 http.formLogin(); //所有的资源请求必须通过认证之后才可以访问 http.authorizeRequests().anyRequest().authenticated(); } }
//!!!向IOC容器中加入认证管理器--不需要表单让用户可隐式登录;适用于密码模式和客户端授权模式,如果只是code模式和简单模式可以不需要添加 @Bean @Override public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); }
授权服务器同时是资源服务器
启动类添加注解:
//开启资源服务器 @EnableResourceServer
Security配置类添加注解
//开启方法级别的授权 @EnableGlobalMethodSecurity(prePostEnabled = true)
controller中声明方法:核心:
@PreAuthorize("hasAnyAuthority('权限')")
//具有user:save权限的用户才可以访问该url接口 @PreAuthorize("hasAnyAuthority('user:save','user:all')") //添加的url接口 @RequestMapping("/save") public String save(){ return "save----ok"; }
访问测试:
#请求测试{GET请求}:http://localhost:9999/save #同时携带请求头:"key":"Authorization","value":"bearer ded6d6d4-1aa0-45d5-b91f-b6556425bf78"
访问测试的时候,需要添加请求头Authorization,它的值为"bearer "开头,后接token,才能发送请求 报错: # "error": "unauthorized", # "error_description": "Full authentication is required to access this resource" 同时在访问时候,需要注意controller中方法上的权限,权限不足也会报错
测试请求发送:
#获取code:GET请求{code模式} http://localhost:9999/oauth/authorize?response_type=code&client_id=baidu&state=hello&redirect_uri=https://www.baidu.com #请求参数response_type值为code,表示向授权服务器申请code码。 #请求参数client_id值为第三方应用id,和配置类中配置的第三方应用的id一致。 #请求参数state状态值,维护请求和回调之间的状态的不透明值,防止跨站点请求伪造,自定义。 #请求参数redirect_uri访问成功后的重定向地址,和配置类中配置的第三方应用的地址一致。 #获取的code在访问成功后的路径中:https://www.baidu.com/?code=Ct0npN&state=all
#携带code申请token:POST请求{code模式} http://localhost:9999/oauth/token?grant_type=authorization_code&code=GAIEXe&redirect_uri=https://www.baidu.com #请求参数grant_type为授权方式,值authorization_code为code码方式。 #请求参数code值为code码。 #请求参数redirect_uri值为成功后的重定向地址,和配置类中配置的第三方应用的地址一致。 #请求头authorization中还需携带标识第三方应用的id和密码。 #获取的token为返回的JSON字符串, #{ # "access_token": "7b34e0cb-7a21-401a-a052-bcfab575848b", # "token_type": "bearer", # "expires_in": 7199, # "scope": "all" #}
#直接获取token,GET请求{简单模式} http://localhost:9999/oauth/authorize?response_type=token&client_id=baidu&state=hello&redirect_uri=https://www.baidu.com #请求参数response_type值为token,表示向授权服务器申请token。 #请求参数client_id值为第三方应用id,和配置类中配置的第三方应用的id一致。 #请求参数state状态值,维护请求和回调之间的状态的不透明值,防止跨站点请求伪造,自定义。 #请求参数redirect_uri访问成功后的重定向地址,和配置类中配置的第三方应用的地址一致。 #https://www.baidu.com/#access_token=7b34e0cb-7a21-401a-a052-bcfab575848b&token_type=bearer&state=hello&expires_in=6573&scope=all #与申请code的区别:需要修改返回值的类型,申请code的返回值为code,申请token,type则为token;同时第三方应用的id和重定向的地址也需要对应的修改
#直接获取token,POST请求{password模式} http://localhost:9999/oauth/token?grant_type=password&username=admin&password=123 #http://localhost:9999/oauth/token固定url,ip、端口为授权服务器ip、端口。 #请求参数grant_type为授权方式,值password为密码方式。 #请求参数username和password为用户的用户名和密码。 #请求头authorization中还需携带标识第三方应用的id和密码 #{ # "access_token": "ded6d6d4-1aa0-45d5-b91f-b6556425bf78", # "token_type": "bearer", # "expires_in": 7173, # "scope": "all" #}
#客户端模式=={一般用于服务之间的调用,信任程度较高情况下使用} #POST请求 http://localhost:9999/oauth/token?grant_type=client_credentials #http://localhost:9999/oauth/token固定url,ip、端口为授权服务器ip、端口。 #请求参数grant_type为授权方式,值client_credentials为客户端方式。 #请求头authorization中还需携带标识第三方应用的id和密码。 #{ # "access_token": "0320d8c2-8af2-4cc4-8e81-a454a3ff67b2", # "token_type": "bearer", # "expires_in": 7199, # "scope": "all" #}
资源服务器
第一步:
给授权服务器的controller中添加方法
//处理/getUser的请求,响应封装了当前认证的用户信息的Principal对象 @RequestMapping("/getUser") public Principal getUser(Principal principal){ return principal; }
第二步:
pom文件
<!--Oauth2.0的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency>
第三步:
启动类添加注解:@EnableResourceServer
//开启资源服务器 @EnableResourceServer
第四步:
配置文件
#资源服务器的服务名称 spring.application.name=pay-service #资源服务器的端口 server.port=8080 #访问授权服务器的/getUser接口,拿到当前认证的用户的信息: #1.可拿到授权服务器给当前认证的用户颁发的token,和用户请求资源服务器时携带的 # token对比是否相同,以决定用户是否可访问资源服务器; #2.还可拿到当前认证的用户的权限等信息; security.oauth2.resource.user-info-uri=http://localhost:9999/getUser
第五步:
创建security的配置类,继承ResourceServerConfigurerAdapter类,并添加注解
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class ResourceConfig extends ResourceServerConfigurerAdapter { //处理请求 @Override public void configure(HttpSecurity http) throws Exception { // /hello接口执行放行 http.authorizeRequests().antMatchers("/hello").permitAll(); //其它接口还是需要认证通过后才可以访问 http.authorizeRequests().anyRequest().authenticated(); } }
第六步:
controller层
//方法上添加注解,给定权限 @PreAuthorize("hasAnyAuthority('权限名')")
授权服务器和资源服务器分离后,测试访问
第一步:
向授权服务器发送请求,拿到token。
第二步:
携带token访问资源服务器
访问测试:
#请求测试;password模式{GET请求}:http://localhost:9999/save #同时携带请求头:"key":"Authorization","value":"bearer ded6d6d4-1aa0-45d5-b91f-b6556425bf78"
访问测试的时候,需要添加请求头Authorization,它的值为"bearer "开头,后接token,才能发送请求 报错: # "error": "unauthorized", # "error_description": "Full authentication is required to access this resource" 同时在访问时候,需要注意controller中方法上的权限,权限不足也会报错
补充:
授权服务器和资源服务器分离情况下,可以访问授权服务器中声明的**/getUser**接口来获取当前登录用户的一些信息
授权服务器=={JWT版}–{对称加密}
将token放入redis中,当并发量较大时候,每次访问资源服务器,都会访问授权服务器校验token是否存在,会使得授权服务器的压力会很大;
所以出现了使用JWT来保存token,第三方应用先通过授权服务器进行认证,认证通过授权服务器向第三方应用颁发jwt的token,然后第三方应用每次访问资源服务器的资源都携带jwt的token,资源服务器通过对第三方应用携带的jwt的token进行解析,以判断第三方应用是否认证通过。这样资源服务器就不需要每次都去访问授权服务器了,进而可以大大减轻授权服务器的压力。
使用:
和使用redis的步骤差不多,只需要修改关于授权服务器的oauth配置类的代码+资源服务器的oauth配置类的代码+资源服务器的配置文件即可
修改授权服务器的配置类
/** * 注入加密器,加密器可以找上边的,应该配过 */ @Autowired private PasswordEncoder passwordEncoder; /** * 注入JwtAccessTokenConverter类型对象 */ @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; /** * 注入认证管理器,认证管理器在Security的配置类中重写authenticationManager()方法 * 添加个@bean注解就行 */ @Autowired private AuthenticationManager authenticationManager; /** * 创建JwtTokenStore对象,构造方法需要传入JwtAccessTokenConverter类型的对象, * 所以在这里先创建JwtAccessTokenConverter类型的对象,并注入到容器 */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ //创建JwtAccessTokenConverter类型的对象 JwtAccessTokenConverter jwtAccessTokenConverter=new JwtAccessTokenConverter(); //设置加密的密钥字符串,也就是jwt字符串的签名部分 jwtAccessTokenConverter.setSigningKey("jwt-abc"); return jwtAccessTokenConverter; } /** 指定token的存储方式: 向容器中加入TokenStore的bean对象;使用JWT来存储的话,返回JwtTokenStore */ @Bean public TokenStore tokenStore(){ //返回匿名JwtTokenStore对象 return new JwtTokenStore(jwtAccessTokenConverter); } /** *重写方法参数为ClientDetailsServiceConfigurer对象的方法,配置第三方应用 */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //第三方较少时,一般就在内存中配置 clients.inMemory() //配置第三方id(字符串标识,随便给只要不重复) .withClient("baidu") //配置第三方密码 -- 通过id和密码来匹配第三方 .secret(passwordEncoder.encode("baidu-secret")) //作用域(没有实质意义,只是个提示,可以随便给) .scopes("all") //授权模式--code码模式 //如果要修改为其余模式{简化模式===implicit;密码模式===password;客户端模式===client_credentials;code模式===authorization_code} .authorizedGrantTypes("password") //token的过期时间--两小时,单位秒 .accessTokenValiditySeconds(7200) //授权成功后重定向的url地址(必须是https协议,且是公网地址[百度 新浪...]) .redirectUris("https://www.baidu.com") ; } /** * 重写方法参数为AuthorizationServerEndpointsConfigurer对象的configure方法 * 这个方法用来配置向资源服务器暴露token的存储方式 */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //暴露存储token的JwtTokenStore对象 endpoints.tokenStore(tokenStore()); //给授权服务器暴露认证处理器 endpoints.authenticationManager(authenticationManager); //给授权服务器暴露jwt的token转换器 endpoints.accessTokenConverter(jwtAccessTokenConverter); }
修改资源服务器
@Configuration //开启方法级别的授权 @EnableGlobalMethodSecurity(prePostEnabled = true) public class ResourceConfig extends ResourceServerConfigurerAdapter { /** * 注入JwtAccessTokenConverter类型对象 */ @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; /** * 创建JwtTokenStore对象,构造方法需要传入JwtAccessTokenConverter类型的对象, * 所以在这里先创建JwtAccessTokenConverter类型的对象,并注入到容器 */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ //创建JwtAccessTokenConverter类型的对象 JwtAccessTokenConverter jwtAccessTokenConverter=new JwtAccessTokenConverter(); //设置加密的密钥字符串,也就是jwt字符串的签名部分 jwtAccessTokenConverter.setSigningKey("jwt-abc"); return jwtAccessTokenConverter; } /** 指定token的存储方式: 向容器中加入TokenStore的bean对象;使用JWT来存储的话,返回JwtTokenStore */ @Bean public TokenStore tokenStore(){ //返回匿名JwtTokenStore对象 return new JwtTokenStore(jwtAccessTokenConverter); } //向资源服务器暴露存储token的TokenStore对象 --- JwtTokenStore @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.tokenStore(tokenStore()); } //处理请求 @Override public void configure(HttpSecurity http) throws Exception { // /hello接口执行放行 http.authorizeRequests().antMatchers("/hello").permitAll(); //其它接口还是需要认证通过后才可以访问 http.authorizeRequests().anyRequest().authenticated(); } }
同时,资源服务器端的配置文件中,也不需要再配置查询的接口了,注释/删除 #security.oauth2.resource.user-info-uri=http://localhost:9999/userInfo
授权服务器=={JWT版}–{非对称加密}
借助OpenSSL工具,授权服务器使用私钥加密生成token,资源服务器使用公钥解密验证token
**私钥生成:**Keytool -genkeypair -alias mmy-jwt -validity 3650 -keyalg RSA –dname “CN=jwt,OU=jtw,O=jwt,L=zurich,S=zurich,C=CH” -keypass mmy123 -keystore mmy-jwt.jks -storepass mmy123
高亮内容都是自定义的,mmy-jwt为别名,mmy-jwt.jks为存放私钥的文件名,mmy123为密码;会生成一个文件,后缀为jks。
**公钥生成:**keytool -list -rfc --keystore mmy-jwt.jks | openssl x509 -inform pem -pubkey
mmy-jwt.jks为存放私钥的文件名。会直接显示一大堆的字符串,截取public-key部分,在资源服务器的resource目录下创建文件,存入那些字符串
只需要基于对称加密的代码,修改授权服务器的oauth的配置类代码+资源服务器的oauth配置类代码即可
授权服务器的配置类代码,重写该方法
@Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ //1)创建jwt的token转换器 JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); //2)从私钥文件读取私钥 //1]使用ClassPathResource加载类路径(resources)下的资源 ClassPathResource classPathResource = new ClassPathResource("私钥文件名.jks"); //2]创建钥匙工厂(构造器参数一是加载了秘钥文件的ClassPathResource对象,参数二是解析秘钥的密码[字符数组形式]) KeyStoreKeyFactory keyFactory = new KeyStoreKeyFactory(classPathResource, "mmy123".toCharArray()); //3]由钥匙工厂解析出秘钥(对象) KeyPair keyPair = keyFactory.getKeyPair("mmy-jwt"); //3)给jwt的token转换器设置秘钥 jwtAccessTokenConverter.setKeyPair(keyPair); return jwtAccessTokenConverter; }
资源服务器的配置类代码,重写该方法,并导入hutool的依赖
@Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ //1)创建jwt的token转换器 JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); //2)读取公钥 //1]也是使用ClassPathResource加载公钥文件 ClassPathResource classPathResource = new ClassPathResource("public-key.txt"); //2]使用hutool工具包的FileUtil工具类从公钥文件中读取公钥(字符串文本) String publicKey = ""; try { publicKey = FileUtil.readString(classPathResource.getFile(), Charset.defaultCharset()); } catch (IOException e) { e.printStackTrace(); } //3)给jwt的token转换器设置公钥 jwtAccessTokenConverter.setVerifierKey(publicKey); return jwtAccessTokenConverter; }
<!-- 借助hutool工具包的FileUtil来读取文件 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.11</version> </dependency>