Oauth2.0的基本使用

发布于:2022-12-16 ⋅ 阅读:(753) ⋅ 点赞:(0)

关于Oath2.0

oauth2.0主要的两方面的工作:

  1. 进行认证===登录 授权(CRUD权限)—security
  2. 授权—给第三方应用办法token—说明第三方应用已经得到了我认证了,之后只要携带我们给它颁发的token就可以访问微服务集群中的服务了

四种授权模式

  1. code码模式=====authorization_code

    步骤:

    1. 认证
    2. 申请服务端,申请颁发code码
    3. 申请服务端,申请颁发token
    4. 携带token访问资源服务器

    思路:当用户同意授权后,授权服务端会先向第三方返回code码,第三方再携带code码来申请token,授权服务端最后再返回token。第三方再携带token来访问资源服务器中的资源

  2. 简化模式=====implicit

    步骤:

    1. 认证
    2. 申请服务端,申请颁发token
    3. 携带token访问资源服务器

    思路:当第三方发出请求申请颁发token,授权服务端返回token。第三方再携带token来访问资源服务器中的资源

  3. 密码模式=====password

    步骤:

    1. 携带资源拥有者的账号和密码直接申请服务端,申请颁发token
    2. 携带token访问资源服务器
  4. 客户端模式======client_credentials

    步骤:

    1. 直接向服务端发出申请,申请颁发token
    2. 携带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 {
}

配置三个属性:

  1. 指定token的存储方式 – 这里选择redis。

        //注入redis连接工厂,在项目启动时候会自动注入,不需要自己手动使用@Bean来注入
        @Autowired
        private RedisConnectionFactory redisConnectionFactory;
    
    	/*
         指定token的存储方式:
         向容器中加入TokenStore的bean对象;因为是先存到redis,所以是RedisTokenStore;如果是使用JWT来存储的话,则返回JwtTokenStore对象
         */
        @Bean
        public TokenStore tokenStore(){
            return new RedisTokenStore(redisConnectionFactory);
        }
    
  2. 配置第三方应用 – 只有配置的第三方应用才可向授权服务器申请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();
        }
    
  3. 向资源服务器暴露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 {
}

配置三个属性:

  1. 指定token的存储方式 – 这里选择redis。

        //注入redis连接工厂,在项目启动时候会自动注入,不需要自己手动使用@Bean来注入
        @Autowired
        private RedisConnectionFactory redisConnectionFactory;
    
    	/*
         指定token的存储方式:
         向容器中加入TokenStore的bean对象;因为是先存到redis,所以是RedisTokenStore;如果是使用JWT来存储的话,则返回JwtTokenStore对象
         */
        @Bean
        public TokenStore tokenStore(){
            return new RedisTokenStore(redisConnectionFactory);
        }
    
  2. 配置第三方应用 – 只有配置的第三方应用才可向授权服务器申请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();
        }
    
  3. 向资源服务器暴露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配置类的代码+资源服务器的配置文件即可

  1. 修改授权服务器的配置类

        /**
         * 注入加密器,加密器可以找上边的,应该配过
         */
        @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);
        }
    
  2. 修改资源服务器

    @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配置类代码即可

  1. 授权服务器的配置类代码,重写该方法

        @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;
        }
    
  2. 资源服务器的配置类代码,重写该方法,并导入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>
    
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到