OAuth2与 JWT 做认证授权服务(四)

OAuth2与 JWT 做认证授权服务

为了保证服务对外的安全性,往往都会在服务接口采用权限校验机制,为了防止客户端在发起请求中途被篡改数据等安全方面的考虑,还会有一些签名校验的机制。
在分布式微服务架构的系统中,我们把原本复杂的系统业务拆分成了若干个独立的微服务应用,我们不得不在每个微服务中都实现这样一套校验逻辑,这样就会有很多的代码和功能冗余,随着服务的扩大和业务需求的复杂度不断变化,修改校验逻辑变得相当麻烦,一处改,处处改。所以我们需要把认证授权服务单独出来,做成一个服务进行调用。

先新建springboot服务 myuaa.添加依赖

 <!--rest接口依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--数据库依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <!--自定义配置依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

application.yml


#服务器配置
server:
  #端口
  port: 9999

#服务器发现注册配置
eureka:
  client:
    serviceUrl:
      #配置服务中心(可配置多个,用逗号隔开)
      defaultZone: http://${user.name}:${user.password}@192.168.0.112:8761/eureka
#spring配置
spring:
  #应用配置
  application:
    #名称: OAuth2认证授权服务
    name: myuaa
  #数据库配置
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://192.168.0.61:3307/myuaa?useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    hikari:
      auto-commit: false
      data-source-properties:
        cachePrepStmts: true
        prepStmtCacheSize: 250
        prepStmtCacheSqlLimit: 2048
        useServerPrepStmts: true

user:
  name: admin
  password: admin_1

我这里用的是mysql 加 mybatis,这些都不是什么问题.数据库框架自己选择
主要是我将用户与角色持久化到数据库中,方便管理
user表

public class User {
    private Long id;
    private String login;  //用户
    private String password;  //密码(因为OAuth2较新的版本都是需要密码加密的,存入时记得加密)
    private String roles;  //角色,多个角色以","分隔, 须臾springsecurity对应,			
    						//"ROLE_ADMIN,ROLE_USER"

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getLogin() {
        return login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getRoles() {
        return roles;
    }

    public void setRoles(String roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", login='" + login + '\'' +
                ", password='" + password + '\'' +
                ", roles='" + roles + '\'' +
                '}';
    }
}

密码加密存储,较新版本必须加密,在后面配置中必须加密,这里与后面一致

import org.springframework.security.crypto.password.PasswordEncoder;

	@Autowired
	private	UserMapper userMapper;//dao层
	@Autowired
    private  PasswordEncoder passwordEncoder;

  	@Override
    public void createUser(User user) {
        String password = user.getPassword();
        user.setPassword(passwordEncoder.encode(password));//密码加密
        userMapper.createUser(user);
    }

然后做一个UserDetailsService的实现类.这个接口是做springsecurity 认证数据填入
DomainUserDetailsService

@Component
public class DomainUserDetailsService implements UserDetailsService {
    private UserMapper userMapper;

    public DomainUserDetailsService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

	//实现方法,从数据库加载用户,返回一个UserDetails 
    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
        User userByLogin = userMapper.getUserByLogin(login);//拿到数据库中对应用户
        if (null==userByLogin){
            throw  new UsernameNotFoundException("用户:"+login+"不存在!!!");
        }
        String roles = userByLogin.getRoles();
        if (null==roles){
            throw  new RoleNotFoundException("用户:"+login+"没有角色.....");//这个异常是我自定义的
        }
        String[] split = roles.split(",");//拿到所有角色
        List<GrantedAuthority> collect = new ArrayList<>() ;//权限集合
        for (String str:split
             ) {
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(str);
            collect.add(simpleGrantedAuthority);
        }
        return new org.springframework.security.core.userdetails.User(userByLogin.getLogin(), userByLogin.getPassword(),collect);
    }
}

现在做配置类,先建一个MyuaaSecurityConfiguration 安全配置类


/**
 * 创建一个Servlet Filter:springSecurityFilterChain,最后在授权中注册
 * 重要的是在一个添加了 @EnableWebSecurity或@EnableGlobalMethodSecurity							
 * 或者 @EnableGlobalAuthentication注解的类里面,注入 AuthenticationManagerBuilder。
 */

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MyuaaSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private  DomainUserDetailsService domainUserDetailsService;

    @Autowired
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    		/**
    		 * 用户验证,这里的AuthenticationManagerBuilder 配置最终影响AuthenticationManager 的生成
    		 */
        auth .userDetailsService(domainUserDetailsService)
                    .passwordEncoder(passwordEncoder());//加密方式,加密,数据库的需要与这一致
    }


    @Bean   //定义密码加密方式,在授权配置引入
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    @Bean      //认证管理器,在授权配置引入
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

//    /**
//     * http安全配置,只做最简单的,根据自己需求修改,这里的配置会被 资源服务器 下的覆盖掉,可以不配
//     *
//     * @param http http安全对象
//     * @throws Exception http安全异常信息
//     */
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        	http
//                .csrf()
//                .disable();//禁用csrf
//    }

//    /**
//     * 配置 spring security 的 custom 链,主要做页面的过滤,可以不配
//     *
//     * @param web
//     * @throws Exception
//     */
//    @Override
//    public void configure(WebSecurity web) throws Exception {
//        web.ignoring()
//                .antMatchers(HttpMethod.OPTIONS, "/**")
//                .antMatchers("/swagger-ui/index.html");
//    }

做授权配置MyuaaServerConfig(核心配置)
MyuaaServerConfig

/**
 * 授权服务器配置
 * @ EnableAuthorizationServer 启用授权服务,用于配置 OAuth 2.0 授权服务器机制,以及实现 AuthorizationServerConfigurer 的任何 @Beans (有一个使用空方法的方便的适配器实现)
 */
@Configuration
@EnableAuthorizationServer
public class MyuaaServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    }

    /**
     * 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{

    }

    /**
     * 用来配置令牌端点(Token Endpoint)的安全约束.
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    }


}

主要是这三个配置,由于我是用的JWT,上面的配置依赖于jwt,所以先生成证书,下面教程链接
链接: 生成证书教程链接.
在Resources 下新建包,将证书放入,记住密码别名OAuth2与 JWT 做认证授权服务(四)_第1张图片
在application.yml中增加自定义配置

uaa:
  key-store:
    name: tls/keystore.p12   #证书位置
    password: admin_1		#密码
    alias: admin			#别名
  web-client-configuration:
    # token过期时间
    access-token-validity-in-seconds: 300
    # 刷新token时间
    refresh-token-validity-in-seconds-for-remember-me: 604800
    client-id: web_app    #自己约定,后期与其他服务用来对接
    secret: changeit		#自己约定

**由于我们添加了自定义配置的依赖,做uaa配置类,主要是prefix 引入uaa根节点


/**
 * Properties for UAA-based OAuth2 security.
 */
@Component
@ConfigurationProperties(prefix = "uaa", ignoreUnknownFields = false)
public class UaaProperties {
    private KeyStore keyStore = new KeyStore();

    public KeyStore getKeyStore() {
        return keyStore;
    }

    private WebClientConfiguration webClientConfiguration = new WebClientConfiguration();

    public WebClientConfiguration getWebClientConfiguration() {
        return webClientConfiguration;
    }

    /**
     * Keystore configuration for signing and verifying JWT tokens.
     */
    public static class KeyStore {
        //name of the keystore in the classpath
        private String name ;
        //password used to access the key
        private String password ;
        //name of the alias to fetch
        private String alias ;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public String getAlias() {
            return alias;
        }

        public void setAlias(String alias) {
            this.alias = alias;
        }
    }

    public static class WebClientConfiguration {
        //validity of the short-lived access token in secs (min: 60), don't make it too long
        private int accessTokenValidityInSeconds ;
        //validity of the refresh token in secs (defines the duration of "remember me")
        private int refreshTokenValidityInSecondsForRememberMe ;
        private String clientId ;
        private String secret ;

        public int getAccessTokenValidityInSeconds() {
            return accessTokenValidityInSeconds;
        }

        public void setAccessTokenValidityInSeconds(int accessTokenValidityInSeconds) {
            this.accessTokenValidityInSeconds = accessTokenValidityInSeconds;
        }

        public int getRefreshTokenValidityInSecondsForRememberMe() {
            return refreshTokenValidityInSecondsForRememberMe;
        }

        public void setRefreshTokenValidityInSecondsForRememberMe(int refreshTokenValidityInSecondsForRememberMe) {
            this.refreshTokenValidityInSecondsForRememberMe = refreshTokenValidityInSecondsForRememberMe;
        }

        public String getClientId() {
            return clientId;
        }

        public void setClientId(String clientId) {
            this.clientId = clientId;
        }

        public String getSecret() {
            return secret;
        }

        public void setSecret(String secret) {
            this.secret = secret;
        }
    }
}

在配置文件MyuaaServerConfig中引入UaaProperties,@

  private UaaProperties uaaProperties;  //uaa自定义配置

    private IatTokenEnhancer iatTokenEnhancer;  //jwt增强器,实现TokenEnhancer,用于在token中添加自定义信息

    private  PasswordEncoder passwordEncoder;//密码加密类,在安全配置定义的bean

    public MyuaaServerConfig(UaaProperties uaaProperties,IatTokenEnhancer iatTokenEnhancer, PasswordEncoder passwordEncoder) {
        this.uaaProperties = uaaProperties;
        this.iatTokenEnhancer = iatTokenEnhancer;
        this.passwordEncoder = passwordEncoder;
    }

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;  // 认证管理器,在security安全配置类中初始化

jwt增强器IatTokenEnhancer ,这种可以写多个,只需实现TokenEnhancer,实现方法

/**
 * 将标准的“iat”声明添加到令牌上,这样我们就能知道它们是什么时候创建的
 * 这是jwt第二个增强器,添加自定义信息
 */
@Component
public class IatTokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        addClaims((DefaultOAuth2AccessToken) accessToken);
        return accessToken;
    }

    private void addClaims(DefaultOAuth2AccessToken accessToken) {
        DefaultOAuth2AccessToken token = accessToken;
        Map<String, Object> additionalInformation = token.getAdditionalInformation();
        if (additionalInformation.isEmpty()) {
            additionalInformation = new LinkedHashMap<String, Object>();
        }
        //将标准的“iat”声明添加到令牌上,这样我们就能知道它们是什么时候创建的
        //这用于非活动会话超时
        additionalInformation.put("iat", new Integer((int)(System.currentTimeMillis()/1000L)));//添加当前时间
        token.setAdditionalInformation(additionalInformation);
    }
}

引入在安全配置MyuaaSecurityConfig中定义的bean:

在配置MyuaaServerConfig的方法前还需定义tokenstore(用于令牌存储)

    /**
     *  令牌存储对象如果用redis或者数据库,万一redis或数据库服务挂了,这个token 就失效了
     *  而JWT自包含的意思就是,JWT令牌本身是有信息的,拿到令牌后,解析令牌就能拿到包含的信息,不用去存储里取。
     *
     *  spring security oauth默认生成的token是uuid
     * 要将uuid换成JWT,需要做两件事,使用JWT ,有两个增强器:
     * 1,第一个叫JwtAccessTokenConverter,作用是添加JWT签名,将uuid的token转为jwt,用秘钥签名
     * 2,第二个叫 TokenEnhancer ,作用是往jwt里添加自定义的信息。由于默认生成uuid token的方法是private,所以通过ImoocJwtTokenEnhancer 往jwt里添加一些自定义的信息
     *    最后拿到增强器链TokenEnhancerChain,判断系统里这两个增强器是不是空,非空的话把这两个增强器连起来,加到TokenEndpoint 。
     *
     *
     * 令牌存储
     * @return redis令牌存储对象
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 生成第一个令牌增强器,用于管理jwt acces令牌和身份验证之间的双向交换
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource(uaaProperties.getKeyStore().getName()), uaaProperties.getKeyStore().getPassword().toCharArray())
                .getKeyPair(uaaProperties.getKeyStore().getAlias());
        converter.setKeyPair(keyPair);
        return converter;
    }

完整的MyuaaServerConfig如下

/**
 * 授权服务器配置
 * @ EnableAuthorizationServer 启用授权服务,用于配置 OAuth 2.0 授权服务器机制,以及实现 AuthorizationServerConfigurer 的任何 @Beans (有一个使用空方法的方便的适配器实现)
 */
@Configuration
@EnableAuthorizationServer
public class MyuaaServerConfig extends AuthorizationServerConfigurerAdapter {

    private UaaProperties uaaProperties;  //uaa自定义配置

    private IatTokenEnhancer iatTokenEnhancer;

    private  PasswordEncoder passwordEncoder; 

    public MyuaaServerConfig(UaaProperties uaaProperties,IatTokenEnhancer iatTokenEnhancer, PasswordEncoder passwordEncoder) {
        this.uaaProperties = uaaProperties;
        this.iatTokenEnhancer = iatTokenEnhancer;
        this.passwordEncoder = passwordEncoder;
    }


    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;  // 认证管理器,在security安全配置类中初始化

	    /**
     *  令牌存储对象如果用redis或者数据库,万一redis或数据库服务挂了,这个token 就失效了
     *  而JWT自包含的意思就是,JWT令牌本身是有信息的,拿到令牌后,解析令牌就能拿到包含的信息,不用去存储里取。
     *
     *  spring security oauth默认生成的token是uuid
     * 要将uuid换成JWT,需要做两件事,使用JWT ,有两个增强器:
     * 1,第一个叫JwtAccessTokenConverter,作用是添加JWT签名,将uuid的token转为jwt,用秘钥签名
     * 2,第二个叫 TokenEnhancer ,作用是往jwt里添加自定义的信息。由于默认生成uuid token的方法是private,所以通过ImoocJwtTokenEnhancer 往jwt里添加一些自定义的信息
     *    最后拿到增强器链TokenEnhancerChain,判断系统里这两个增强器是不是空,非空的话把这两个增强器连起来,加到TokenEndpoint 。
     *
     *
     * 令牌存储
     * @return redis令牌存储对象
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 生成第一个令牌增强器,用于管理jwt acces令牌和身份验证之间的双向交换,使用的非对称加密
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource(uaaProperties.getKeyStore().getName()), uaaProperties.getKeyStore().getPassword().toCharArray())
                .getKeyPair(uaaProperties.getKeyStore().getAlias());
        converter.setKeyPair(keyPair);
        return converter;
    }



    /**
     * GrantTypes5种类型:
     *      authorization_code 授权码模式(即先登录获取code,再获取token)
     *      password 密码模式(将用户名,密码传过去,直接获取token)
     *      client_credentials 客户端模式(无用户,用户向客户端注册,然后客户端以自己的名义向’服务端’获取资源)
     *      implicit 简化模式(在redirect_uri 的Hash传递token; Auth客户端运行在浏览器中,如JS,Flash)
     *      refresh_token 刷新access_token
     *
     * 用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    }

    /**
     * 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    }

    /**
     * 用来配置令牌端点(Token Endpoint)的安全约束.
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    }


}

ClientDetailsServiceConfigurer的配置

 	@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		int accessTokenValidity = uaaProperties.getWebClientConfiguration().getAccessTokenValidityInSeconds();//token有效时长
        int refreshTokenValidity = uaaProperties.getWebClientConfiguration().getRefreshTokenValidityInSecondsForRememberMe();
        refreshTokenValidity = Math.max(refreshTokenValidity, accessTokenValidity);//获得最终token刷新时间
        clients.inMemory() //放在内存中
                .withClient(uaaProperties.getWebClientConfiguration().getClientId())//client_id
                .secret(passwordEncoder.encode(uaaProperties.getWebClientConfiguration().getSecret()))//secret
                .scopes("web-app")//写了的话,客户端要与此一致
                .autoApprove(true)//自动授权,不出页面
                .authorizedGrantTypes("refresh_token", "password", "authorization_code","client_credentials")//"implicit",
                .accessTokenValiditySeconds(accessTokenValidity)//token有效期
                .refreshTokenValiditySeconds(refreshTokenValidity);//refreshtoken有效期
    }

AuthorizationServerEndpointsConfigurer的配置,这里可以引入项目上下文(ApplicationContext)获得所有增强器,但我这里只写了一个,没必要(Collection tokenEnhancers = applicationContext.getBeansOfType(TokenEnhancer.class).values()

   @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();//拿到项目中所有TokenEnhancer(包括自定义实现类)
        tokenEnhancers.add(iatTokenEnhancer);
        tokenEnhancers.add(jwtAccessTokenConverter());//两个增强器都必须加到增强器链
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
        endpoints
                .authenticationManager(authenticationManager)
                .tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancerChain)
                .reuseRefreshTokens(false);
    }

AuthorizationServerSecurityConfigurer配置

  @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll()") //开启token_key路径(拿到jwt公匙)
        			.checkTokenAccess("isAuthenticated()");
    }

配置完成后

/**
 * 授权服务器配置
 * @ EnableAuthorizationServer 启用授权服务,用于配置 OAuth 2.0 授权服务器机制,以及实现 AuthorizationServerConfigurer 的任何 @Beans (有一个使用空方法的方便的适配器实现)
 */
@Configuration
@EnableAuthorizationServer
public class MyuaaServerConfig extends AuthorizationServerConfigurerAdapter {

    private UaaProperties uaaProperties;  //uaa自定义配置

    private IatTokenEnhancer iatTokenEnhancer;

    private  PasswordEncoder passwordEncoder;

    public MyuaaServerConfig(UaaProperties uaaProperties,IatTokenEnhancer iatTokenEnhancer, PasswordEncoder passwordEncoder) {
        this.uaaProperties = uaaProperties;
        this.iatTokenEnhancer = iatTokenEnhancer;
        this.passwordEncoder = passwordEncoder;
    }


    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;  // 认证管理器,在security安全配置类中初始化



    /**
     *  令牌存储对象如果用redis或者数据库,万一redis或数据库服务挂了,这个token 就失效了
     *  而JWT自包含的意思就是,JWT令牌本身是有信息的,拿到令牌后,解析令牌就能拿到包含的信息,不用去存储里取。
     *
     *  spring security oauth默认生成的token是uuid
     * 要将uuid换成JWT,需要做两件事,使用JWT ,有两个增强器:
     * 1,第一个叫JwtAccessTokenConverter,作用是添加JWT签名,将uuid的token转为jwt,用秘钥签名
     * 2,第二个叫 TokenEnhancer ,作用是往jwt里添加自定义的信息。由于默认生成uuid token的方法是private,所以通过ImoocJwtTokenEnhancer 往jwt里添加一些自定义的信息
     *    最后拿到增强器链TokenEnhancerChain,判断系统里这两个增强器是不是空,非空的话把这两个增强器连起来,加到TokenEndpoint 。
     *
     *
     * 令牌存储
     * @return redis令牌存储对象
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 生成第一个令牌增强器,用于管理jwt acces令牌和身份验证之间的双向交换
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource(uaaProperties.getKeyStore().getName()), uaaProperties.getKeyStore().getPassword().toCharArray())
                .getKeyPair(uaaProperties.getKeyStore().getAlias());
        converter.setKeyPair(keyPair);
        return converter;
    }

    /**
     * GrantTypes5种类型:
     *      authorization_code 授权码模式(即先登录获取code,再获取token)
     *      password 密码模式(将用户名,密码传过去,直接获取token)
     *      client_credentials 客户端模式(无用户,用户向客户端注册,然后客户端以自己的名义向’服务端’获取资源)
     *      implicit 简化模式(在redirect_uri 的Hash传递token; Auth客户端运行在浏览器中,如JS,Flash)
     *      refresh_token 刷新access_token
     *
     * 用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        int accessTokenValidity = uaaProperties.getWebClientConfiguration().getAccessTokenValidityInSeconds();//token有效时长
        int refreshTokenValidity = uaaProperties.getWebClientConfiguration().getRefreshTokenValidityInSecondsForRememberMe();
        refreshTokenValidity = Math.max(refreshTokenValidity, accessTokenValidity);//获得最终token刷新时间
        clients.inMemory()
                .withClient(uaaProperties.getWebClientConfiguration().getClientId())
                .secret(passwordEncoder.encode(uaaProperties.getWebClientConfiguration().getSecret()))
                .scopes("web-app")//写了的话,客户端要与此一致
                .autoApprove(true)//自动授权,不出页面
                .authorizedGrantTypes("refresh_token", "password", "authorization_code","client_credentials")//"implicit",
                .accessTokenValiditySeconds(accessTokenValidity)
                .refreshTokenValiditySeconds(refreshTokenValidity);
    }

    /**
     * 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();//拿到项目中所有TokenEnhancer(包括自定义实现类)
        tokenEnhancers.add(iatTokenEnhancer);
        tokenEnhancers.add(jwtAccessTokenConverter());//两个增强器都必须加到增强器链
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
        endpoints
                .authenticationManager(authenticationManager)
                .tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancerChain)
                .reuseRefreshTokens(false);
    }



    /**
     * 用来配置令牌端点(Token Endpoint)的安全约束.
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")    开启/oauth/token_key验证端口无权限访问
                .checkTokenAccess("isAuthenticated()");// 开启/oauth/check_token验证端口认证权限访问
    }


}

这配置完成后就是一个认证服务器,先运行打开postmen,路径/oauth/token_keyOAuth2与 JWT 做认证授权服务(四)_第2张图片
拿到公匙,再拿一下token,路径/oauth/tokenOAuth2与 JWT 做认证授权服务(四)_第3张图片
这里写client_id与secret
OAuth2与 JWT 做认证授权服务(四)_第4张图片
再在body中写grant_type =password(密码模式),用户名,密码(即是密码)
结果OAuth2与 JWT 做认证授权服务(四)_第5张图片

如果出错好好会看一下,如果出来的token比较短,没有加密,看看tokenstore,JwtAccessTokenConverter与AuthorizationServerEndpointsConfigurer的配置

现在做一个简易的资源服务器,即将myuaa作为认证服务器的同时,也可以作为一个资源服务器,一个认证服务器是可以同时配置多个资源服务器
创建配置类MyuaaResourceConfig

/**
 * OAuth2 资源服务配置
 *
 * @author Canaan
 * @date 2019/3/18 14:13
 */
@Configuration
@EnableResourceServer
public class MyuaaResourceConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .exceptionHandling()  //允许配置错误处理
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
            .and()
                .csrf()  //添加csrf支持,默认启用
                .disable() //禁止
//                .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)//在跨域资源共享过滤器之前添加 过滤器
                .headers()  //
                .frameOptions()
                .disable()  //禁止X-frameOptions 添加到响应头
            .and()
                .sessionManagement() //允许配置会话管理
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //会话创建策略---无状态
            .and()
                .authorizeRequests()//允许基于使用HttpServletRequest限制访问
                .antMatchers("/api/**").authenticated();
              
    }

}

这里配置所有/api 开头请求都需要验证
做接口

@RestController
@RequestMapping("/api")
public class HelloResource {

    @GetMapping("hello")
    public String AAA(){
        return "似懂非懂物管费";
    }
}

再运行myuaa,打开postmen
OAuth2与 JWT 做认证授权服务(四)_第6张图片
提示没有权限
我们就给他权限
OAuth2与 JWT 做认证授权服务(四)_第7张图片
/oauth/authorize 授权路径
先选择authorization,选择oauth2 ,填写信息,因为我们在配置client是加入了客户端模式,这里选择Client Credentials,点击RequestToken,生成Oauth2令牌,点击添加到header,这是headers中会多出一项

OAuth2与 JWT 做认证授权服务(四)_第8张图片
请求完成OAuth2与 JWT 做认证授权服务(四)_第9张图片
关于网关与其他资源服务器下章讲

你可能感兴趣的:(springcloud微服务)