美文网首页Spring BootSpring-Boot架构社区
基于 Spring Security OAuth2和 JWT 构

基于 Spring Security OAuth2和 JWT 构

作者: breezedancer | 来源:发表于2018-07-06 16:26 被阅读218次

    我们希望自己的微服务能够在用户登录之后才可以访问,而单独给每个微服务单独做用户权限模块就显得很弱了,从复用角度来说是需要重构的,从功能角度来说,也是欠缺的。尤其是前后端完全分离之后,我们的用户信息不一定存在于 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: 5000
    
    logging:
      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
    @SpringBootApplication
    public 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: true
    server:
      port: 9999
    eureka:
      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
    @EnableEurekaClient
    public 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,他的代码如下,主要是负责用户信息获取的

    @Service
    public 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 两个实体类需要做如下的实现

    @Entity
    public 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;
        }
    
    }
    
    @Entity
    public 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
    @EnableEurekaClient
    public class Application {
    
        public static void main(String[] args) {
            new SpringApplicationBuilder(Application.class)
            .web(true).run(args);
        }
    }
    

    配置资源服务器

    @Configuration        
    @EnableResourceServer
    public 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

    @Configuration
    public 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 {
    }
    

    编写用户相关服务,用户注册和用户登录

    @Service
    public 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是一个默认的处理方式

    @Component
    public 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
    @ResponseBody
    public 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

    @RestController
    public 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

    相关文章

      网友评论

      • CodeSheep:请问是不是还要有一个用户表呢?
        breezedancer:@CodeSheep 是的
      • 鹏_a2a4:出现Consider defining a bean of type 'com.cnsesan.uaa.dao.UserDao' in your configuration.您是怎么解决的呀
        鹏_a2a4:import org.springframework.data.jpa.repository.JpaRepository;
        鹏_a2a4:@breezedancer 导入了呀
        breezedancer:你导入的 jpa 包了吗?
      • 000db234bb96:你那个公钥和私钥哪来的
        breezedancer:@嗯不错啊 就是在命令行输入,是jdk 自带的一个工具
        000db234bb96:@breezedancer 这个命令在哪里输入?有啥效果?
        breezedancer:@嗯不错啊 keytool -genkeypair -alias cnsesan-jwt ...........看这
      • 000db234bb96:Oauth2好复杂啊:sob:
      • Starrier:有源码么
        breezedancer:@Starrier 基本都在文中了

      本文标题:基于 Spring Security OAuth2和 JWT 构

      本文链接:https://www.haomeiwen.com/subject/rwlbuftx.html