0713~oauth2~Day1

什么是oauth2:

1.oauth2为用户授权提供了一个开放,安全,简易的标准;

2.oauth2授权不会使第三方获得到用户的账号信息与密码,用户的安全获得了保障;

security用户的认证与授权用security,微服务与微服务之间的调用需要使用aouth2,每次请求调用其他微服务接口的时候需要携带token;

知识点:hystrix隔离策略:

1.线程池隔离:(默认方式)线程A进,会有B线程接力,返回出线程;

2.信号量隔离:线程大小固定的;


401端口错误:没有认证权限;

通过上下文获得request:RequestcontextHolder.getRequestAttributes();

通过feign调用微服务的时候,上下文路径会没有值,因为A线程进,B线程出;需要配置类重新赋值;

微服务整合security与oauth2

1.security配置类:基于数据库,配置类需要添加两个方法,再创建一个类实现UserDetalsService;

2.oauth2的认证服务,需要配置三个config;

3.oauth2的资源服务,需要两个config配置类;

4.微服务之间用feign调用,需要携带token认证;

 security和oauth2的整合依赖;

 <!--认证授权依赖包(里面包含了security的包)-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

1.security配置类:基于数据库,配置类需要添加两个方法,再创建一个类实现 UserDetalsService;

@Configuration
@EnableWebSecurity//开启Security Web安全配置
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启方法授权注解支持
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    //注册一个认证管理器给Spring,这个是oAuth2的密码模式会用到
    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    //密码编码器
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //授权规则配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()                                //授权配置
                .antMatchers("/login").permitAll()  //登录路径放行
                .anyRequest().authenticated()                   //所有请求都需要登录后才能访问
                .and().formLogin()
                .and().logout().permitAll()                    //登出路径放行 /logout
                .and().csrf().disable();                        //关闭跨域伪造检查
    }
}
@Service
public class MyUserDetailsService implements UserDetailsService {

    @Resource
    private LoginMapper loginMapper;

    @Resource
    private PermissionMapper permissionMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1.username基本判断
        if(!StringUtils.hasLength(username)){
            throw new UsernameNotFoundException("用户名不可为空");
        }
        //2.根据username查询mysql中的认证信息
        Login dbUser = loginMapper.selectByUsername(username);
        if(dbUser == null){
            throw new UsernameNotFoundException("用户名不存在");
        }

        //3.加载用户的权限列表
        List<GrantedAuthority> authorities = new ArrayList<>();
        List<Permission> permissions = permissionMapper.selectPermissionsByUserId(dbUser.getId());
        for (Permission permission : permissions){
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission.getSn());
            System.out.println("加载用户【" + username + "】的权限:" + permission.getSn());
            authorities.add(simpleGrantedAuthority);
        }

        //4.把认证信息和权限信息封装成UserDetails返回
        User userDetails = new User(
                username, //账号
                dbUser.getPassword(), //密码
                dbUser.getEnabled(),  //用户是否已启用
                dbUser.getAccountNonExpired(),  //账户是否过期
                dbUser.getCredentialsNonExpired(), //密码是否过期
                dbUser.getAccountNonLocked(), //账户是否锁定
                authorities); //用户拥有权限集合
        return userDetails;
    }

 2.oauth2的认证服务,需要配置三个config;

@Configuration
//开启授权服务配置
@EnableAuthorizationServer
public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    //1.客户端详情配置==============================
    //数据源
    @Autowired
    private DataSource dataSource ;
    //编码器
    @Autowired
    private PasswordEncoder passwordEncoder;

    //1.1.定义ClientDetailsService 客户端详情配置服务
    @Bean
    public ClientDetailsService clientDetailsService(){
        //JdbcClientDetailsService的作用是去数据库加载客户端配置,加载表 oauth_client_details
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return jdbcClientDetailsService;
    }

    //1.2.配置客户端详情
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //ClientDetailsService:就是提供客户端详情配置的一个服务
        clients.withClientDetails(clientDetailsService());
    }

    //设置JWT签名密钥。它可以是简单的MAC密钥,也可以是RSA密钥
    private final String sign_key  = "itsource";

    //JWT令牌校验工具
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter jwtAccessTokenConverter =
                new JwtAccessTokenConverter();
        //设置JWT签名密钥。它可以是简单的MAC密钥,也可以是RSA密钥
        jwtAccessTokenConverter.setSigningKey(sign_key);
        return jwtAccessTokenConverter;
    }


    //2.服务端点:授权码,令牌管理配置======================
    //2.1.认证管理器
    @Autowired
    private AuthenticationManager authenticationManager;
    //2.2.定义 JdbcAuthorizationCodeServices 授权码的服务,基于数据库存储
    @Bean
    public AuthorizationCodeServices  authorizationCodeServices(){
        return new JdbcAuthorizationCodeServices(dataSource);
    }
    //2.3.定义AuthorizationServerTokenServices ,令牌的服务配置
    @Bean
    public AuthorizationServerTokenServices tokenService(){
        //创建默认的令牌服务
        DefaultTokenServices services = new DefaultTokenServices();
        //指定客户端详情配置
        services.setClientDetailsService(clientDetailsService());
        //支持刷新token
        services.setSupportRefreshToken(true);
        //token存储方式
        services.setTokenStore(tokenStore());
        //设置token增强 - 设置token转换器
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter()));
        services.setTokenEnhancer(tokenEnhancerChain);
        return services;
    }
    //2.4.配置令牌的存储(服务端不需要存token,给客户端存就可以了)
    @Bean
    public TokenStore tokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    //2.5.配置授权码和令牌端点服务
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
            //密码授权模式需要
            .authenticationManager(authenticationManager)
            //授权码模式服务
            .authorizationCodeServices(authorizationCodeServices())
            //配置令牌管理服务
            .tokenServices(tokenService())
            //允许post方式请求
            .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }

    //3.授权服务安全配置,url是否放行等==============================
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                //对应/oauth/token_key路径,我放行
                .tokenKeyAccess("permitAll()")
                //对应/oauth/check_token路径,我放行
                .checkTokenAccess("permitAll()")
                //允许客户端进行表单身份验证,使用表单认证申请令牌
                .allowFormAuthenticationForClients();
    }
}

 3.oauth2的资源服务,需要两个config配置类;

//资源服务配置
@Configuration
//开启资源服务配置
@EnableResourceServer
//开启方法授权
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyResourceServerConfig extends ResourceServerConfigurerAdapter {

    //1.资源服务安全配置

    //JWT令牌
    @Bean
    public TokenStore tokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    //设置JWT签名密钥。它可以是简单的MAC密钥,也可以是RSA密钥
    private final String sign_key  = "itsource";

    //JWT令牌校验工具
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        //设置JWT签名密钥。它可以是简单的MAC密钥,也可以是RSA密钥
        jwtAccessTokenConverter.setSigningKey(sign_key);
        return jwtAccessTokenConverter;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        //资源ID,对应客户端详情表(oauth_client_details)中的resource_ids字段中配置的值
        resources.resourceId("courseId").tokenStore(tokenStore());
    }

    //2.资源服务的资源的授权配置,比如那些资源放行,那些资源需要什么权限等等
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //校验scope必须为hrm , 对应客户端详情表(oauth_client_details)中的scope字段中配置的值
                .antMatchers("/**").access("#oauth2.hasScope('hrm')")
                //关闭跨域伪造检查
                .and().csrf().disable()
                //把session设置为无状态,意思是使用了token,那么session不再做数据的记录
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

 通过feign调用微服务的时候,上下文路径会没有值,因为A线程进,B线程出;需要配置类重新赋值;

/自定义Hystrix隔离策略
@Configuration
public class FeignHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {

    private HystrixConcurrencyStrategy hystrixConcurrencyStrategy;

    public FeignHystrixConcurrencyStrategy() {
        try {
            this.hystrixConcurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (this.hystrixConcurrencyStrategy instanceof FeignHystrixConcurrencyStrategy) {
                return;
            }
            HystrixCommandExecutionHook commandExecutionHook =
                    HystrixPlugins.getInstance().getCommandExecutionHook();
            HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
            HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
            HystrixPropertiesStrategy propertiesStrategy =
                    HystrixPlugins.getInstance().getPropertiesStrategy();

            HystrixPlugins.reset();
            HystrixPlugins instance = HystrixPlugins.getInstance();
            instance.registerConcurrencyStrategy(this);
            instance.registerCommandExecutionHook(commandExecutionHook);
            instance.registerEventNotifier(eventNotifier);
            instance.registerMetricsPublisher(metricsPublisher);
            instance.registerPropertiesStrategy(propertiesStrategy);
        } catch (Exception e) {
            System.out.println("策略注册失败");
        }
    }

    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        //这里在A线程,还未分配新的线程,这里我获取请求
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //这里相当于是开一个新的B线程,将请求放到B线程
        return new WrappedCallable<>(callable, requestAttributes);
    }

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                            HystrixProperty<Integer> corePoolSize,
                                            HystrixProperty<Integer> maximumPoolSize,
                                            HystrixProperty<Integer> keepAliveTime,
                                            TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        return this.hystrixConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,
                unit, workQueue);
    }

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                            HystrixThreadPoolProperties threadPoolProperties) {
        return this.hystrixConcurrencyStrategy.getThreadPool(threadPoolKey, threadPoolProperties);
    }

    @Override
    public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
        return this.hystrixConcurrencyStrategy.getBlockingQueue(maxQueueSize);
    }

    @Override
    public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
        return this.hystrixConcurrencyStrategy.getRequestVariable(rv);
    }

    static class WrappedCallable<T> implements Callable<T> {
        private final Callable<T> target;
        private final RequestAttributes requestAttributes;

        public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
            this.target = target;
            this.requestAttributes = requestAttributes;
        }

        @Override
        public T call() throws Exception {
            try {
                //这里是B线程,把A线程传入进来的请求放入B线程的 RequestContextHolder 中
                RequestContextHolder.setRequestAttributes(requestAttributes);
                return target.call();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        }
    }
}

 4.微服务之间用feign调用,需要携带token认证;

//Feign拦截器
@Component
public class TokenInterceptor implements RequestInterceptor {

    //请求头中的token名称
    private final String AUTHORIZATION_HEADER = "Authorization";

    @Override
    public void apply(RequestTemplate template) {

        //1、获取请求对象,使用RequestContextHolder获取
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();

        ///2.从请求头获取到令牌
        String token = request.getHeader(AUTHORIZATION_HEADER);

        //3.把令牌添加到 Feign 的请求头
        if(StringUtils.hasLength(token)){
            template.header(AUTHORIZATION_HEADER, token);
        }
    }
}

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值