java电商项目(八)

本文涉及的产品
实时数仓Hologres,5000CU*H 100GB 3个月
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
简介: OAuth 2.0 是一种开放标准,允许用户授权第三方应用访问其在某一网站上的私密资源,而无需提供用户名和密码。它通过提供一个令牌(token)来实现这一功能。OAuth 2.0 主要包括四种授权模式:授权码模式、简化模式、密码模式和客户端模式。授权码模式是最常用的一种,适用于第三方平台登录功能。Spring Security OAuth 2.0 提供了强大的工具来实现授权服务器和资源服务器的集成,支持多种授权模式和令牌存储方式,如内存、数据库、JWT 和

[TOC]

1 Oauth2 介绍

1.1 什么是 OAuth 2

  • OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如头像、照片、视频等),而在这个过程中无须将用户名和密码提供给第三方应用。实现这一功能是通过提供一个令牌(token),而不是用户名和密码来访问他们存放在特定服务提供者的数据。
  • 每一个令牌授权一个特定的网站在特定的时段内访问特定的资源。这样,OAuth 让用户可以授权第三方网站灵活地访问存储在另外一些资源服务器的特定信息,而非所有内容。目前主流的 qq,微信等第三方授权登录方式都是基于 OAuth2 实现的。
  • OAuth 2OAuth 协议的下一版本,但不向下兼容 OAuth 1.0OAuth 2 关注客户端开发者的简易性,同时为 Web 应用、桌面应用、移动设备、起居室设备提供专门的认证流程。
  • 传统的 Web 开发登录认证一般都是基于 Session 的,但是在前后端分离的架构中继续使用 Session 会有许多不便,因为移动端(AndroidiOS、微信小程序等)要么不支持Cookie(微信小程序),要么使用非常不便,对于这些问题,使用 OAuth 2 认证都能解决。

1.2 OAuth 2 授权流程

1558175981680

如图11-1所示,授权步骤有下面6歩

  • 步骤1:客户端(第三方应用)向用户请求授权。
  • 步骤2:用户单击客户端所呈现的服务授权页面上的同意授权按钮后,服务端返回一个授权许可凭证给客户端。
  • 步骤3:客户端拿着授权许可凭证去授权服务器申请令牌。
  • 步骤4:授权服务器验证信息无误后,发放令牌给客户端。
  • 步骤5:客户端拿着令牌去资源服务器访问资源。
  • 步骤6:资源服务器验证令牌无误后开放资源。

1.3 OAuth 2 角色

资源所有者Resource Owner):即代表授权客户端访问本身资源信息的用户,客户端访问用户帐户的权限仅限于用户授权的“范围”。

客户端Client):即代表意图访问受限资源的第三方应用。在访问实现之前,它必须先经过用户者授权,并且获得的授权凭证将进一步由授权服务器进行验证。

授权服务器Authorization Server):授权服务器用来验证用户提供的信息是否正确,并返回一个令牌给第三方应用。

资源服务器Resource Server):资源服务器是提供给用户资源的服务器,例如头像、照片、视频等。

1.4 OAuth 2 授权模式

OAuth 协议的授权模式共分为 4 种,分别说明如下:

  • 授权码模式:授权码模式(authorization code)是功能最完整、流程最严谨的授权模式。它的特点就是通过客户端的服务器与授权服务器进行交互,国内常见的第三方平台登录功能基本 都是使用这种模式。
  • 简化模式:简化模式不需要客户端服务器参与,直接在浏览器中向授权服务器中请令牌,一般若网站是纯静态页面,则可以采用这种方式。
  • 密码模式:密码模式是用户把用户名密码直接告诉客户端,客户端使用这些信息向授权服务器中请令牌。这需要用户对客户端高度信任,例如客户端应用和服务提供商是同一家公司。
  • 客户端模式:客户端模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请授权。严格来说,客户端模式并不能算作 OAuth 协议要解决的问题的一种解决方案,但是,对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用这种模式还是非常方便的。

2 Spring Security Oauth2.0 入门案例

2.1 授权服务器

工程结构如下图:

2.1.1 搭建授权工程

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.lxs</groupId>
    <artifactId>auth-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>auth-server</name>


    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR5</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

启动器配置文件

com\lxs\oauth2\AuthServerApplication.java

package com.lxs.oauth2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

@SpringBootApplication
@EnableResourceServer
public class AuthServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthServerApplication.class, args);
    }

}

oauth2-demo\auth-server\src\main\resources\application.yml

server:
  port:
    8888

2.1.2 配置类

Oauth2配置类

完整代码

com\lxs\oauth2\config\OAuth2Config.java

package com.lxs.oauth2.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;


@Configuration
//开启授权服务
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    private static final String CLIENT_ID = "cms";
    private static final String SECRET_CHAR_SEQUENCE = "{noop}secret";
    private static final String SCOPE_READ = "read";
    private static final String SCOPE_WRITE = "write";
    private static final String TRUST = "trust";
    private static final String USER ="user";
    private static final String ALL = "all";
    private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 30*60;
    private static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 30*60;
    // 密码模式授权模式
    private static final String GRANT_TYPE_PASSWORD = "password";
    //授权码模式
    private static final String AUTHORIZATION_CODE = "authorization_code";
    //refresh token模式
    private static final String REFRESH_TOKEN = "refresh_token";
    //简化授权模式
    private static final String IMPLICIT = "implicit";
    //指定哪些资源是需要授权验证的
    private static final String RESOURCE_ID = "resource_id";

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                // 使用内存存储
                .inMemory()
                //标记客户端id
                .withClient(CLIENT_ID)
                //客户端安全码
                .secret(SECRET_CHAR_SEQUENCE)
                //为true 直接自动授权成功返回code
                .autoApprove(true)
                .redirectUris("http://127.0.0.1:8084/cms/login") //重定向uri
                //允许授权范围
                .scopes(ALL)
                //token 时间秒
                .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
                //刷新token 时间 秒
                .refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS)
                //允许授权类型
                .authorizedGrantTypes(GRANT_TYPE_PASSWORD,AUTHORIZATION_CODE);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 使用内存保存生成的token
        endpoints.authenticationManager(authenticationManager).tokenStore(memoryTokenStore());
    }

    /**
     * 认证服务器的安全配置
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                //.realm(RESOURCE_ID)
                // 开启/oauth/token_key验证端口认证权限访问
                .tokenKeyAccess("isAuthenticated()")
                //  开启/oauth/check_token验证端口认证权限访问
//                .checkTokenAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()")
                //允许表单认证
                .allowFormAuthenticationForClients();
    }

    @Bean
    public TokenStore memoryTokenStore() {
        // 最基本的InMemoryTokenStore生成token
        return new InMemoryTokenStore();
    }

}

Spring Security配置类

授权配置分析

完整代码

com\lxs\oauth2\config\SecurityConfig.java

package com.lxs.oauth2.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;


@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {    //auth.inMemoryAuthentication()
        auth.inMemoryAuthentication()
                .withUser("lxs")
                .password("{noop}123")
                .roles("admin");
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/asserts/**");
        web.ignoring().antMatchers("/favicon.ico");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http   // 配置登录页并允许访问
                .formLogin().permitAll()
                // 配置Basic登录
                //.and().httpBasic()
                // 配置登出页面
                .and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
                // 配置允许访问的链接
                .and().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**", "/api/**").permitAll()
                // 其余所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                // 关闭跨域保护;
                .and().csrf().disable();
    }

}

2.1.3 测试

测试验证令牌的端点

2.2 资源服务器

2.2.1 搭建资源服务器

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.lxs</groupId>
    <artifactId>cms</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cms</name>


    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR4</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

启动器配置文件

com\lxs\oauth2\CmsApplication.java

package com.lxs.oauth2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

@SpringBootApplication
@EnableResourceServer
public class CmsApplication {

    public static void main(String[] args) {
        SpringApplication.run(CmsApplication.class, args);
    }

}

application.properties

server.port=8084
server.servlet.context-path=/cms

2.2.2 配置类

Oauth2资源服务器配置类

完整代码

package com.lxs.oauth2.config;

import java.io.IOException;
import javax.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;


@Configuration
public class Oauth2ResourceServerConfiguration extends
    ResourceServerConfigurerAdapter {

  private static final String CHECK_TOKEN_URL = "http://localhost:8888/oauth/check_token";

  @Override
  public void configure(ResourceServerSecurityConfigurer resources) {

    RemoteTokenServices tokenService = new RemoteTokenServices();

//    tokenService.setRestTemplate(restTemplate);

    tokenService.setCheckTokenEndpointUrl(CHECK_TOKEN_URL);
    tokenService.setClientId("cms");
    tokenService.setClientSecret("secret");

//    DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
//    defaultAccessTokenConverter.setUserTokenConverter(new CustomUserAuthenticationConverter());
//    tokenService.setAccessTokenConverter(defaultAccessTokenConverter);

    resources.tokenServices(tokenService);
  }

}

spring security配置类

package com.lxs.oauth2.config;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @Des 乐购商城项目
 * @Author 雪松
 * @Date 2020/10/14 14:13
 */
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/**").authenticated();
    // 禁用CSRF
    http.csrf().disable();
  }
}

2.2.3 Controller

1个Controller用于测试

package com.lxs.oauth2.controller;

import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;

/**
 * @Des 乐购商城项目
 * @Author 雪松
 * @Date 2020/10/14 15:05
 */
@RestController
public class HelloController {

    @GetMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication) {
        return authentication;
    }

    @GetMapping("/index")
    public String index() {
        return "index";
    }
}

2.2.4 测试

3 Oauth2授权模式

Oauth2有以下授权模式回顾:

OAuth 协议的授权模式共分为 4 种,分别说明如下:

  • 授权码模式:授权码模式(authorization code)是功能最完整、流程最严谨的授权模式。它的特点就是通过客户端的服务器与授权服务器进行交互,国内常见的第三方平台登录功能基本 都是使用这种模式。
  • 简化模式:简化模式不需要客户端服务器参与,直接在浏览器中向授权服务器中请令牌,一般若网站是纯静态页面,则可以采用这种方式。
  • 密码模式:密码模式是用户把用户名密码直接告诉客户端,客户端使用这些信息向授权服务器中请令牌。这需要用户对客户端高度信任,例如客户端应用和服务提供商是同一家公司。
  • 客户端模式:客户端模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请授权。严格来说,客户端模式并不能算作 OAuth 协议要解决的问题的一种解决方案,但是,对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用这种模式还是非常方便的。

3.1 授权码模式

3.1.1 授权码模式流程

授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌1。授权码模式功能最完整、使用最广泛、流程最严密的授权模式

第三方授权一般就是授权码模式,流程如下:

  • (A):客户端携带client_id、redirect_uri,中间通过代理者访问授权服务器,如果已经登录过会直接返回redirect_uri,没有登录过就跳转到登录页面
  • (B)授权服务器对客户端进行身份验证(通过用户代理,让用户输入用户名和密码)
  • (C)授权通过,会重定向到redirect_uri并携带授权码code作为uri参数
  • (D)客户端携带授权码访问授权服务器
  • (E)验证授权码通过,返回acceptToken

3.1.2 认证服务授权码配置

4.1.3 功能测试

(1)申请授权码

访问授权链接,在浏览器访问就可以,授权码模式response_type参数传code:

Get请求:
http://localhost:8888/oauth/authorize?client_id=cms&client_secret=secret&response_type=code

参数列表如下:

client_id:客户端id,和授权配置类中设置的客户端id一致。 
response_type:授权码模式固定为code 
scop:客户端范围,和授权配置类中设置的scop一致。 
redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)

因为没登录,所以会返回SpringSecurity的默认登录页面,具体代码是http .formLogin().permitAll();,如果要弹窗登录的,可以配置http.httpBasic();,这种配置是没有登录页面的,自定义登录页面可以这样配置http.formLogin().loginPage("/login").permitAll(),参考OAuth2Config代码

如图,输入SpringSecurity配置的静态账号密码:lxs/123

1565035634997

登录成功,返回redirect_uri,拿到授权码

(2)申请令牌

拿到授权码后,申请令牌。 使用Postman申请授权码

grant_type:授权类型,填写authorization_code,表示授权码模式 
code:授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。 
redirect_uri:申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。

Base64(cms:secret) = WGNXZWJBcHA6WGNXZWJBcHA=WGNXZWJBcHA6WGNXZWJBcHA=

此链接需要使用 http Basic认证。 什么是http Basic认证? http协议定义的一种认证方式,将客户端id和客户端密码按照“客户端ID:客户端密码”的格式拼接,并用base64编 码,放在header中请求服务端,一个例子: Authorization:Basic WGNXZWJBcHA6WGNXZWJBcHA=WGNXZWJBcHA6WGNXZWJBcHA= 是用户名:密码的base64编码。 认证失败服务端返回 401 Unauthorized。

以上测试使用postman完成:

http basic认证:

1558182132328

1558182177167

客户端Id和客户端密码会匹配数据库oauth_client_details表中的客户端id及客户端密码。

点击发送: 申请令牌成功

返回信如下:

access_token:访问令牌,携带此令牌访问资源 
token_type:有MAC Token与Bearer Token两种类型,两种的校验算法不同,RFC 6750建议Oauth2采用 Bearer Token(http://www.rfcreader.com/#rfc6750)。 
refresh_token:刷新令牌,使用此令牌可以延长访问令牌的过期时间。 
expires_in:过期时间,单位为秒。 
scope:范围,与定义的客户端范围一致。    
jti:当前token的唯一标识
(3)令牌校验

Spring Security Oauth2提供校验令牌的端点,如下:

Get: 
http://localhost:8888/oauth/check_token?token=171ce96e-7492-4a27-becd-8ccbdc69666b

(4)使用令牌

使用正确令牌访问/index服务

不用令牌访问/index服务

使用错误令牌访问

3.2 简化模式

3.2.1 简化模式流程

简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此称简化模式。简化模式是相对于授权码模式而言的

简化模式,流程如下:

  • (A):客户端携带client_id、redirect_uri,中间通过代理者访问授权服务器,如果已经登录过会直接返回redirect_uri,没有登录过就跳转到登录页面
  • (B)授权服务器对客户端进行身份验证(通过用户代理,让用户输入用户名和密码)
  • (C)授权通过,会重定向到redirect_uri并携带授权码token作为uri参数
  • (D)客户端携带授权码访问资源服务器
  • (E)验证token通过,返回资源

3.2.2 认证服务授权码配置

3.2.3 功能测试

(1)申请令牌

访问授权链接,在浏览器访问就可以,授权码模式response_type参数传token:

Get请求:
http://localhost:8888/oauth/authorize?client_id=cms&redirect_uri=http://127.0.0.1:8084/cms/login&response_type=token&scope=all

参数列表如下:

client_id:客户端id,和授权配置类中设置的客户端id一致。 
response_type:简化模式固定为token
scop:客户端范围,和授权配置类中设置的scop一致。 
redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址

因为没登录,所以会返回SpringSecurity的默认登录页面,具体代码是http .formLogin().permitAll();,如果要弹窗登录的,可以配置http.httpBasic();,这种配置是没有登录页面的,自定义登录页面可以这样配置http.formLogin().loginPage("/login").permitAll(),参考OAuth2Config代码

如图,输入SpringSecurity配置的静态账号密码:lxs/123

1565035634997

登录成功,返回redirect_uri,直接拿到令牌

(2)令牌校验

Spring Security Oauth2提供校验令牌的端点,如下:

Get: 
http://localhost:8888/oauth/check_token?token=171ce96e-7492-4a27-becd-8ccbdc69666b

(3)使用令牌

使用正确令牌访问/index服务

不用令牌访问/index服务

使用错误令牌访问

简化模式和授权码模式的区别

授权码模式User-agent(浏览器)只是持有授权码(code)使用授权码获得令牌,授权码,只能校验一次,这样即使授权码泄露,令牌相对安全,而简化模式由user agent(浏览器),直接持有令牌,相对不安全

3.3 密码模式

密码模式(resource owner password credentials):密码模式中,用户向客户端提供自己的用户名和密码,这通常用在用户对客户端高度信任的情况

密码授权一般就是授权码模式,流程如下:

  • (A)用户访问客户端,提供URI连接包含用户名和密码信息给授权服务器
  • (B)授权服务器对客户端进行身份验证
  • (C)授权通过,返回acceptToken给客户端

3.3.1 密码模式配置

3.3.2 功能测试

(1)申请令牌
grant_type:授权类型,填写password,表示密码模式
username:用户名
password:密码

此链接需要使用 http Basic认证。 什么是http Basic认证? http协议定义的一种认证方式,将客户端id和客户端密码按照“客户端ID:客户端密码”的格式拼接,并用base64编 码,放在header中请求服务端,一个例子: Authorization:Basic WGNXZWJBcHA6WGNXZWJBcHA=WGNXZWJBcHA6WGNXZWJBcHA= 是用户名:密码的base64编码。 认证失败服务端返回 401 Unauthorized。

以上测试使用postman完成:

http basic认证:

1558182132328

1558182177167

客户端Id和客户端密码会匹配数据库oauth_client_details表中的客户端id及客户端密码。

点击发送: 申请令牌成功

返回信如下:

access_token:访问令牌,携带此令牌访问资源 
token_type:有MAC Token与Bearer Token两种类型,两种的校验算法不同,RFC 6750建议Oauth2采用 Bearer Token(http://www.rfcreader.com/#rfc6750)。 
refresh_token:刷新令牌,使用此令牌可以延长访问令牌的过期时间。 
expires_in:过期时间,单位为秒。 
scope:范围,与定义的客户端范围一致。    
jti:当前token的唯一标识
(2)令牌校验

Spring Security Oauth2提供校验令牌的端点,如下:

Get: 
http://localhost:8888/oauth/check_token?token=1e628350-5711-4983-9b10-da7a7e8b9558

(3)使用令牌

使用正确令牌访问/index服务

不用令牌访问/index服务

使用错误令牌访问

3.4 客户端模式

3.4.1 客户端模式流程

客户端模式(client credentials):客户端模式(client credentials)适用于没有前端的命令行应用,即在命令行下请求令牌

客户端模式,流程如下:

  • 第一步: 获取tokenhttp://localhost:8888/oauth/token?client_id=cms&client_secret=secret&grant_type=client_credentials&scope=all
  • 第二步:拿到acceptToken之后,就可以直接访问资源

3.4.2 认证服务授权码配置

3.4.3 功能测试

(1)申请令牌

访问授权链接,在浏览器访问就可以,授权码模式response_type参数传token:

post请求:
http://localhost:8888/oauth/token?client_id=cms&client_secret=secret&grant_type=client_credentials&scope=all

参数列表如下:

client_id:客户端id,和授权配置类中设置的客户端id一致。
client_secret:客户端秘钥,和授权配置类中设置的客户端secret一致。
response_type:密码模式固定为client_credentials
scop:客户端范围,和授权配置类中设置的scop一致。

(2)令牌校验

Spring Security Oauth2提供校验令牌的端点,如下:

Get: 
http://localhost:8888/oauth/check_token?token=171ce96e-7492-4a27-becd-8ccbdc69666b

(3)使用令牌

使用正确令牌访问/index服务

不用令牌访问/index服务

使用错误令牌访问

这种模式跟密码模式一样,同样适用于前端分离的微服务架构中实现SSO

3.5 令牌存储方式

对于token存储有如下方式,分别进行介绍:

  • InMemoryTokenStore,默认存储,保存在内存
  • JdbcTokenStore,access_token存储在数据库
  • JwtTokenStore,JWT这种方式比较特殊,这是一种无状态方式的存储,不进行内存、数据库存储,只是JWT中携带全面的用户信息,保存在jwt中携带过去校验就可以,系统中采用JwtTokenStore
  • RedisTokenStore,将 access_token 存到 redis 中。
  • JwkTokenStore,将 access_token 保存到 JSON Web Key。

如果实现无状态,认证服务器不存储Token,可以采用JwtTokenStore,我们的乐购商城项目中采用的是JWT方式存储token,如果想使用redis或者jdbc数据库存储token,可以参考网上的文章,这里不再讲解

4 商城项目授权中心微服务

授权中心微服务使用密码授权模式,颁发令牌。

乐购商城项目的Oauth2认证服务器采用JwtTokenStore处理token,也就是认证服务器器不存储token,实现纯粹的无状态token管理,同时应该spring security oauth2的脚本规范,使用数据库方式存储客户端信息

4.1 导入数据库脚本

项目中使用数据库脚本存储客户端信息

4.2 搭建项目

创建auth-center授权中心微服务工程,采用密码模式颁发令牌授权

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <parent>
        <artifactId>legou-parent</artifactId>
        <groupId>com.lxs</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>auth-center</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>

        <!--oauth2-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
            <scope>runtime</scope>
        </dependency>


        <dependency>
            <groupId>com.lxs</groupId>
            <artifactId>legou-security-instance</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <nonFilteredFileExtensions>
                        <nonFilteredFileExtension>cert</nonFilteredFileExtension>
                        <nonFilteredFileExtension>jks</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

auth-center/src/main/java/com/service/auth/serviceauth/AuthApplication.java

package com.service.auth.serviceauth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

@SpringBootApplication
@EnableResourceServer
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class  AuthApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class, args);
    }
}

auth-center/src/main/resources/bootstrap.yml

spring:
  application:
    name: auth-center
  main:
    allow-bean-definition-overriding: true

#logging:
#  level:
#    root: debug

config-repo/auth-center.yml

server:
  port: 9098

logging:
  level:
    org.springframework.security: DEBUG

4.3 自定义UserDetailsService

我们颁发的令牌,会携带用户信息,包括角色,这些用户信息,需要使用RBAC微服务查询获得,然后通过JWT令牌载荷存储,所以这里我们按照spring security规范实现自定义UserDetailsService,获取用户角色权限RBAC数据,存储到令牌

使用自定义的UserDetailsService访问RBAC数据

访问RBAC微服务需要使用Feign调用RBAC微服务

auth-center/src/main/java/com/service/auth/serviceauth/client/UserClient.java

package com.service.auth.serviceauth.client;

import com.lxs.legou.security.api.UserApi;
import com.lxs.legou.security.po.Role;
import com.lxs.legou.security.po.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@FeignClient(name = "security-service", fallback = UserClient.UserClientFallback.class)
public interface UserClient extends UserApi {

    @Component
    @RequestMapping("/fallback") //这个可以避免容器中requestMapping重复
    class UserClientFallback implements UserClient {

        private static final Logger LOGGER = LoggerFactory.getLogger(UserClientFallback.class);

        @Override
        public User getByUserName(String userName) {
            LOGGER.info("异常发生,进入fallback方法");
            return null;
        }

        @Override
        public List<Role> selectRolesByUserId(Long id) {
            LOGGER.info("异常发生,进入fallback方法");
            return null;
        }
    }

}

auth-center/src/main/java/com/service/auth/serviceauth/service/impl/UserDetailServiceImpl.java

package com.service.auth.serviceauth.service.impl;

import com.lxs.legou.security.po.Role;
import com.service.auth.serviceauth.client.UserClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

//自定义userdetailservice
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    private static final Logger logger = LoggerFactory.getLogger(UserDetailServiceImpl.class);

    @Autowired
    UserClient userClient;

    @Autowired
    PasswordEncoder passwordEncoder;//BCryptPasswordEncoder

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        com.lxs.legou.security.po.User user = userClient.getByUserName(username);
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        if (user != null) {
            logger.debug("current user = " + user);
            //获取用户的授权
            List<Role> roles = userClient.selectRolesByUserId(user.getId());
            //声明授权文件
            for (Role role : roles) {
                if (role != null && role.getName() != null) {
                    GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_" + role.getName());//spring Security中权限名称必须满足ROLE_XXX
                    grantedAuthorities.add(grantedAuthority);
                }
            }
        }
        logger.debug("granted authorities = " + grantedAuthorities);
        return new User(user.getUserName(), user.getPassword(), grantedAuthorities);
    }
}

4.4 配置类

oauth2配置类

完整代码

auth-center/src/main/java/com/service/auth/serviceauth/config/AuthorizationServerConfiguration.java

package com.service.auth.serviceauth.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import javax.sql.DataSource;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(AuthorizationServerConfiguration.class);

    @Autowired
    @Qualifier("authenticationManagerBean")
    AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;//配置文件配置的数据库信息

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("lxs.jks"), "123456".toCharArray());//证书路径和密钥库密码
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("lxs"));//密钥别名
        return converter;
    }

    @Bean//声明 ClientDetails实现
    public ClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Override//配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
/*     // 配置两个客户端,一个用于password认证一个用于client认证
        clients.jdbc(dataSource)
                .withClient("client_1")
                .authorizedGrantTypes("client_credentials", "refresh_token")
                .scopes("select")
                .authorities("oauth2")
                .secret(new BCryptPasswordEncoder().encode("123456"))
                .and().withClient("client_2")
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("server")
                .authorities("oauth2")
                .secret(new BCryptPasswordEncoder().encode("123456"));*/
        //直接读取数据库,需要保证数据库配置有客户端信息(oauth_client_details),否则资源服务器无法获取认证数据
        clients.withClientDetails(clientDetailsService());

    }

    @Override//配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services),还有token的存储方式(tokenStore)
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtAccessTokenConverter()).authenticationManager(authenticationManager);

        // 配置tokenServices参数
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(false);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30天
        endpoints.tokenServices(tokenServices);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 允许表单认证
        security.allowFormAuthenticationForClients()
                // 开启/oauth/token_key验证端口无权限访问
                .tokenKeyAccess("permitAll()")
                // 开启/oauth/check_token验证端口认证权限访问
                .checkTokenAccess("isAuthenticated()");
    }
}

spring security配置类

完整代码

auth-center/src/main/java/com/service/auth/serviceauth/config/SecurityConfiguration.java

package com.service.auth.serviceauth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

   @Autowired
    private UserDetailsService userDetailsService;//注入自定义userdetailservice(com.service.auth.serviceauth.service.impl.UserDetailServiceImpl)

    @Bean
    PasswordEncoder passwordEncoder() {
       return new BCryptPasswordEncoder();
        //return PasswordEncoderFactories.createDelegatingPasswordEncoder();//兼容多种密码的加密方式
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().anyRequest()//所有请求都加入HttpSecurity(多个HttpSecurity过滤)
                .and().authorizeRequests().antMatchers("/oauth/**").permitAll();//开放/oauth/开头的所有请求
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());//注入自定义的UserDetailsService,采用BCrypt加密
    }
}

4.6 测试

5 非对称加密

非对称加密算法需要两个密钥:公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。公钥私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。 非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将公钥公开,需要向甲方发送信息的其他角色(乙方)使用该密钥(甲方的公钥)对机密信息进行加密后再发送给甲方;甲方再用自己私钥对加密后的信息进行解密。甲方想要回复乙方时正好相反,使用乙方的公钥对数据进行加密,同理,乙方使用自己的私钥来进行解密。

5.1 公钥私钥

5.1.1 公钥私钥原理

张三有两把钥匙,一把是公钥,另一把是私钥。

1562470657125

张三把公钥送给他的朋友们----李四、王五、赵六----每人一把。

1562470847506

5.1.1.1 公钥加密私钥解密

注:这个过程我们项目中没有涉及和使用

李四要给张三写一封保密的信。她写完后用张三的公钥加密,就可以达到保密的效果。

1562471063950

张三收信后,用私钥解密,就看到了信件内容。这里要强调的是,只要张三的私钥不泄露,这封信就是安全的,即使落在别人手里,也无法解密。

1562471429501

5.1.1.2 私钥签名令牌公钥验证

项目中的JWT就是使用这种模式

张三给李四回信,决定采用"数字签名",注:这个过程就是JWT的签名防篡改。他写完后先用Hash函数,生成信件的摘要(digest)。张三将这个签名,附在信件下面,一起发给李四。

1562472383322

李四收信后,取下数字签名,用张三的公钥解密,得到信件的摘要。由此证明,这封信确实是张三发出的。李四再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。

1562472956514

5.2 资源服务授权流程

(1)传统授权流程

1562479275302

资源服务器授权流程如上图,客户端先去授权服务器申请令牌,申请令牌后,携带令牌访问资源服务器,资源服务器访问授权服务校验令牌的合法性,授权服务会返回校验结果,如果校验成功会返回用户信息给资源服务器,资源服务器如果接收到的校验结果通过了,则返回资源给客户端。

传统授权方法的问题是用户每次请求资源服务,资源服务都需要携带令牌访问认证服务去校验令牌的合法性,并根 据令牌获取用户的相关信息,性能低下。

(2)公钥私钥授权流程

1562406193809

传统的授权模式性能低下,每次都需要请求授权服务校验令牌合法性,我们可以利用公钥私钥完成对令牌的加密,如果加密解密成功,则表示令牌合法,如果加密解密失败,则表示令牌无效不合法,合法则允许访问资源服务器的资源,解密失败,则不允许访问资源服务器资源。

上图的业务流程如下:

1、客户端请求认证服务申请令牌
2、认证服务生成令牌认证服务采用非对称加密算法,使用私钥生成令牌。
3、客户端携带令牌访问资源服务客户端在Http header 中添加: Authorization:Bearer 令牌。
4、资源服务请求认证服务校验令牌的有效性资源服务接收到令牌,使用公钥校验令牌的合法性。
5、令牌有效,资源服务向客户端响应资源信息

5.2.2 生成私钥公钥

我们采用JWT私钥颁发令牌,公钥校验令牌,这里先试用keytool工具生成公钥私钥证书

(1)生成密钥证书 下边命令生成密钥证书,采用RSA 算法每个证书包含公钥和私钥

创建一个文件夹,在该文件夹下执行如下命令行:

keytool -genkeypair -alias lxsong -keyalg RSA -keypass lxsong -keystore lxsong.jks -storepass lxsong

Keytool 是一个java提供的证书管理工具

-alias:密钥的别名 
-keyalg:使用的hash算法 
-keypass:密钥的访问密码 
-keystore:密钥库文件名,xc.keystore保存了生成的证书 
-storepass:密钥库的访问密码

(2)查询证书信息

keytool -list -keystore lxsong.jks

(3)删除别名

keytool -delete -alias lxsong -keystore lxsong.jsk

5.2.3 导出公钥

openssl是一个加解密工具包,这里使用openssl来导出公钥信息。

安装 openssl:http://slproweb.com/products/Win32OpenSSL.html

安装资料目录下的Win64OpenSSL-1_1_0g.exe

配置openssl的path环境变量,如下图:

1565037190408

本教程配置在C:\OpenSSL-Win64\bin

cmd进入lxsong.jks文件所在目录执行如下命令(如下命令在windows下执行,会把-变成中文方式,请将它改成英文的-):

keytool -list -rfc --keystore lxsong.jks | openssl x509 -inform pem -pubkey

1558222853425

下面段内容是公钥

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvFsEiaLvij9C1Mz+oyAm
t47whAaRkRu/8kePM+X8760UGU0RMwGti6Z9y3LQ0RvK6I0brXmbGB/RsN38PVnh
cP8ZfxGUH26kX0RK+tlrxcrG+HkPYOH4XPAL8Q1lu1n9x3tLcIPxq8ZZtuIyKYEm
oLKyMsvTviG5flTpDprT25unWgE4md1kthRWXOnfWHATVY7Y/r4obiOL1mS5bEa/
iNKotQNnvIAKtjBM4RlIDWMa6dmz+lHtLtqDD2LF1qwoiSIHI75LQZ/CNYaHCfZS
xtOydpNKq8eb1/PGiLNolD4La2zf0/1dlcr5mkesV570NxRmU1tFm8Zd3MZlZmyv
9QIDAQAB
-----END PUBLIC KEY-----

将上边的公钥拷贝到文本public.key文件中,合并为一行,可以将它放到需要实现授权认证的工程中。

5.2.4 JWT令牌

(1)创建令牌数据

在auth-center工程中创建测试类JWTTest,使用它来创建令牌信息,代码如下:

auth-center/src/test/java/com/service/auth/serviceauth/JWTTest.java

package com.service.auth.serviceauth;

import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.test.context.junit4.SpringRunner;

import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.*;

public class JwtTest {
   

    /**
     * 使用私钥生成令牌
     */
    @Test
    public void testCreateJwt() throws Exception {
   
        //存储秘钥的工厂对象
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mickey.jks"), "mickey".toCharArray());
        //密钥对(公钥-》私钥)
        KeyPair keyPair = keyStoreKeyFactory.getKeyPair("mickey", "mickey".toCharArray());
        //私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        //自定义payload信息
        Map<String, Object> tokenMap = new HashMap<>();
        tokenMap.put("id", 123);
        tokenMap.put("name", "mickey");
        tokenMap.put("roles", "r01, r02, admin");
        //使用工具类,通过私钥颁发JWT令牌
        Jwt jwt = JwtHelper.encode(new ObjectMapper().writeValueAsString(tokenMap), new RsaSigner(privateKey));
        String token = jwt.getEncoded();
        System.out.println(token);
    }

}

运行后的结果如下:

token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6IlJPTEVfVklQLFJPTEVfVVNFUiIsIm5hbWUiOiJpdGhlaW1hIiwiaWQiOiIxIn0.IR9Qu9ZqYZ2gU2qgAziyT38UhEeL4Oi69ko-dzC_P9-Vjz40hwZDqxl8wZ-W2WAw1eWGIHV1EYDjg0-eilogJZ5UikyWw1bewXCpvlM-ZRtYQQqHFTlfDiVcFetyTayaskwa-x_BVS4pTWAskiaIKbKR4KcME2E5o1rEek-3YPkqAiZ6WP1UOmpaCJDaaFSdninqG0gzSCuGvLuG40x0Ngpfk7mPOecsIi5cbJElpdYUsCr9oXc53ROyfvYpHjzV7c2D5eIZu3leUPXRvvVAPJFEcSBiisxUSEeiGpmuQhaFZd1g-yJ1WQrixFvehMeLX2XU6W1nlL5ARTpQf_Jjiw"

(2)解析令牌

上面创建令牌后,我们可以对JWT令牌进行解析,这里解析需要用到公钥,我们可以将之前生成的公钥public.key拷贝出来用字符串变量token存储,然后通过公钥解密。

在legou-user-oauth创建测试类com.legou.token.ParseJwtTest实现解析校验令牌数据,代码如下:

    /**
     * 使用公钥校验令牌
     */
    @Test
    public void testVerify() {
   
        //令牌
        String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6InIwMSwgcjAyLCBhZG1pbiIsIm5hbWUiOiJtaWNrZXkiLCJpZCI6MTIzfQ.LPQKnZmAdj_9mp8KPWjEeEfmJ2SQmbIZvR2oBQ1A8Ze1xDISY4G2IYWQcPCW7D0Y7rrQEqf1j9YikA8kQIQdybQmXEn9Jtd7HPUHgCUyVLukJ3-g34kMzCzrBDCtuNXzD3PfNElBk9FRTOnKG1_4Rzn0nVGWyFOsQcb8aTR9ch-5hTGHeJ-S_G0ttJpwAktO8x_OMQTSqAV99f0WvXtV14_e-8LoFSVKjawarmCY9tabHDudnWljA7xL-5qjSSUvUs4hoon6IwvhRRERjJU0jvUxAQVPOeXgauANkRCBEFX3Yjt63Z_UOfTzSkSQQcz5tgUBCWHk1gAeG6gYzghf3w";
        //公钥
        String publicKey = "-----BEGIN PUBLIC KEY-----\n" +
                "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhoe+ze0O4CKGc7k9U5dJ\n" +
                "FJFnBeWh3Pcx8VDuL+SXIzGDKADMU2zo1f/80pvCCuXYqHhu5CQ3wzeBGx7BvR3v\n" +
                "jeuWnp5GQArAplOCaDDFKfD8Dxyq9kaCSUn6IBX33k3uMbWmJqACd9gEq1eJWaQL\n" +
                "mh46eAn2Kvb5i1UZH3t6MrOIbyPIva59BZsvhek4ZsPxdzC3SgbKqxIlIF69bSoh\n" +
                "M76fJnJGYnpNRg3CmbfEl/no3dEYST/4WgWVJ9M0DIjqN4tbWN7xrKzcXAX9Y4mh\n" +
                "IJvnxh88RfKBPQD5X04a04OHYusjczoy8quVkr/agRlyeuLnFOJolmGgjBxU1nLn\n" +
                "cQIDAQAB\n" +
                "-----END PUBLIC KEY-----";
        //校验令牌
        Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publicKey));
        String claims = jwt.getClaims();
        System.out.println(claims);

    }

运行后的结果如下:

{"ext":"1","roles":"r01,r02","name":"mrt","id":"123"}
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHQiOiIxIiwicm9sZXMiOiJyMDEscjAyIiwibmFtZSI6Im1ydCIsImlkIjoiMTIzIn0.OXzFObxUq35--qgvBy4mnBXx-f9mfpYMczTfAfH7yHM05W-oJ6RPmLPonZsFlZMd8JBdLm6iz_TN6b4ynO0heCBsyML2ZLx0sxhgE28mhztDXj2GHbWu3kwsRzU9Pbgy-CO3FIG0Iw-aIkFSivaaLsCju5oOLxGB825ueI5hM58sPLLykZPAaU6DcVY3X1sfpWDIQ7G7JkCoP3rH385Vcmg1VBJVIwVxEn4TXHtWqre9lgK-T7D4zXlhScB57gv9OfcbebNm8tI2Rew1IHmOCeKf5CKAiSCv5d26LhLPKqvGBQ5Cy67JM58X2T-4LvgQeQR6TZmiiSr7fLnEkNe9KQ

6 商城项目资源服务器

6.1 资源服务器配置

1562406193809

基本上所有微服务都是资源服务

授权服务器采用私钥加密生成令牌,对外向资源服务器提供公钥,资源服务器使 用公钥 来校验令牌的合法性。 将公钥拷贝到 public.key文件中,将此文件拷贝到每一个需要的资源服务工程的classpath下 ,例如:RBAC微服务.

6.1.2 RBAC微服务资源授权

将上面生成的公钥public.key拷贝到legou-security-service微服务工程的resources目录下,如下图:

1562476672902

引入依赖

在legou-security-service微服务工程pom.xml中引入oauth依赖

<!--oauth依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

配置文件

配置访问授权服务器的公钥的端点

legou-parent\config-repo\security-service.yml

security:
  oauth2:
    resource:
      jwt:
        key-uri: http://localhost:9098/oauth/token_key    #如果使用JWT,可以获取公钥用于 token 的验签

配置使用公钥校验令牌

legou-security/legou-security-service/src/main/java/com/lxs/legou/security/config/JwtConfig.java

package com.lxs.legou.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.util.FileCopyUtils;

import java.io.IOException;

@Configuration
public class JwtConfig {
   

    public static final String public_cert = "public.key";

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Bean
    @Qualifier("tokenStore")
    public TokenStore tokenStore() {
   
        return new JwtTokenStore(jwtAccessTokenConverter);
    }

    @Bean
    protected JwtAccessTokenConverter jwtAccessTokenConverter() {
   
        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(e);
        }

        converter.setVerifierKey(publicKey); //设置校验公钥

        converter.setSigningKey("lxsong"); //设置证书签名密码,否则报错

        return converter;
    }
}

配置资源服务器

legou-security/legou-security-service/src/main/java/com/lxs/legou/security/config/ResourceServerConfiguration.java

package com.lxs.legou.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
   

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(HttpSecurity http) throws Exception {
   
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/user/**", "/security/user/**").permitAll()
                .antMatchers("/book/**").hasRole("ADMIN") //用于测试
                .antMatchers("/**").authenticated();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
   
        resources.tokenStore(tokenStore);
    }
}

6.1.3 授权测试

1562406193809

用户每次访问微服务的时候,需要先申请令牌,令牌申请后,每次将令牌放到头文件中,才能访问微服务。

头文件中每次需要添加一个Authorization头信息,头的结果为bearer token

(1) 创建测试用的Controller

package com.lxs.legou.security.controller;

import com.lxs.legou.security.utils.BPwdEncoderUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

@RestController
public class TestEndPointController {

    @GetMapping("/product/{id}")
    public String getProduct(@PathVariable String id) {
        return "product id : " + id;
    }

    @GetMapping("/order/{id}")
    public String getOrder(@PathVariable String id) {
        return "order id : " + id;
    }

    @GetMapping("/book/{id}")
    public String getBook(@PathVariable String id) {
        return "book id : " + id;
    }

    @GetMapping("/getPrinciple")
    public OAuth2Authentication getPrinciple(OAuth2Authentication oAuth2Authentication, Principal principal, Authentication authentication) {
        logger.info(oAuth2Authentication.getUserAuthentication().getAuthorities().toString());
        logger.info(oAuth2Authentication.toString());
        logger.info("principal.toString() " + principal.toString());
        logger.info("principal.getName() " + principal.getName());
        logger.info("authentication: " + authentication.getAuthorities().toString());

        return oAuth2Authentication;
    }

}

(2)不携带令牌测试

访问http://localhost:18089/order/1 不携带令牌,结果如下:

1562476959697

(2)携带正确令牌访问

访问http://localhost:9002/order/123 携带正确令牌,结果如下:

1562477314274

(3)携带错误令牌

访问http://localhost:9002/order/123 携带不正确令牌,结果如下:

1562477383130

6.2 SpringSecurity权限控制

由于我们项目使用了微服务,任何用户都有可能使用任意微服务,此时我们需要控制相关权限,例如:普通用户角色不能使用用户的删除操作,只有管理员才可以使用,那么这个时候就需要使用到SpringSecurity的权限控制功能了。

6.2.1 角色加载

在auth-center服务中,auth-center/src/main/java/com/service/auth/serviceauth/service/impl/UserDetailServiceImpl.java该类实现了加载用户相关信息,如下代码:

1562735467555

数据库中的数据如下:

只用id=15的用户admin属于管理员

6.2.2 角色权限控制

在每个微服务中,需要获取用户的角色,然后根据角色识别是否允许操作指定的方法,Spring Security中定义了四个支持权限控制的表达式注解,分别是@PreAuthorize@PostAuthorize@PreFilter@PostFilter。其中前两者可以用来在方法调用前或者调用后进行权限检查,后两者可以用来对集合类型的参数或者返回值进行过滤。在需要控制权限的方法上,我们可以添加@PreAuthorize注解,用于方法执行前进行权限检查,校验用户当前角色是否能访问该方法。

(1)开启@PreAuthorize

legou-security的ResourceServerConfiguration类上添加@EnableGlobalMethodSecurity注解,用于开启@PreAuthorize的支持,代码如下:

1562736198378

(2)方法权限控制

legou-security微服务的TestEndPointController类的测试方法上添加权限控制注解@PreAuthorize,代码如下:

1562736271600

同时也可以在资源服务配置类中配置,进行权限控制,如下图:

(3)测试

我们使用Postman测试,分别获得管理员和刘国梁的令牌,其中管理员用户属于管理员角色,刘国梁不属于管理员角色

这时使用刘国梁用户的令牌访问/anno/123,拒绝访问

1562736397379

发现上面无法访问,因为用户登录的时候,刘国梁用户包含admin角色,而anno方法需要admin角色,所以被拦截了。

我们再测试其他方法,其他方法没有配置拦截,所以用户登录后就会放行。如下

访问http://localhost:9002/order/123

效果如下:

1562736457828

使用管理员用户访问/anno/123,则有权限放行可以访问

知识点说明:

如果希望一个方法能被多个角色访问,配置:@PreAuthorize("hasAnyAuthority('admin','user')")

如果希望一个类都能被多个角色访问,在类上配置:@PreAuthorize("hasAnyAuthority('admin','user')")

7 登录实现

7.1 登录流程

登录流程

  • 前端提交用户名、密码
  • 用户微服务得到用户名密码
  • 用户微服务组织数据包括:client_id:client_secrect组织Basic Authorization,用户名,密码等参数
  • 用户微服务使用restTemplate发送HTTP请求给授权中心微服务
  • 授权中心微服务校验通过颁发令牌
  • 前端将令牌令牌存储到sessionStorage中,下次访问资源服务器通过Header携带访问

1558224020588

7.2 配置文件

在用户微服务工程中配置

pom.xml中增加oauth构件依赖

        <!--oauth2-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

legou-security.yml中增加如下配置

配置文件完整代码

server:
  port: 9002

mybatis-plus:
  mapper-locations: classpath*:mybatis/*/*.xml
  type-aliases-package: com.lxs.legou.*.po
  configuration:
    # 下划线驼峰转换
    map-underscore-to-camel-case: true
    lazy-loading-enabled: true
    aggressive-lazy-loading: false

logging:
  #file: demo.log
  pattern:
    console: "%d - %msg%n"
  level:
    org.springframework.web: debug
    com.lxs: debug

security:
  oauth2:
    resource:
      jwt:
        key-uri: http://localhost:9098/oauth/token_key    #如果使用JWT,可以获取公钥用于 token 的验签
    client:
      access-token-uri: http://localhost:9098/oauth/token    #令牌端点
      user-authorization-uri: http://localhost:9098/oauth/authorize    #授权端点
      client-id: client
      client-secret: 123456
      grant-type: password
      scope: read,write

UserController对应的读取配置信息的对象

7.3 配置类

在spring容器中创建BCryptPasswordEncoder加密加密组件,处理密码

package com.lxs.legou.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig {

    @Bean
    PasswordEncoder passwordEncoder() {
       return new BCryptPasswordEncoder();
        //return PasswordEncoderFactories.createDelegatingPasswordEncoder();//兼容多种密码的加密方式
    }

}

7.4 业务层

保存用户使用BCryptPasswordEncoder处理密码

7.5 控制层

package com.lxs.legou.security.controller;

import com.lxs.legou.core.controller.BaseController;
import com.lxs.legou.core.po.ResponseBean;
import com.lxs.legou.security.dto.UserLoginParamDto;
import com.lxs.legou.security.po.Role;
import com.lxs.legou.security.po.User;
import com.lxs.legou.security.service.IUserService;
import com.lxs.legou.security.utils.BPwdEncoderUtil;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2ClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import javax.validation.Valid;
import java.util.Base64;
import java.util.Collections;
import java.util.List;


@RestController
@RequestMapping(value = "/user")
public class UserController extends BaseController<IUserService, User> {
   

    @Autowired
    private OAuth2ClientProperties oAuth2ClientProperties;

    @Autowired
    private OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails;

    @Autowired
    private RestTemplate restTemplate;

    @Bean
    public RestTemplate restTemplate() {
   
        return new RestTemplate();
    }

    @RequestMapping("/login")
    public ResponseEntity<OAuth2AccessToken> login(String username, String password) {
   
        //1:验证用户
        User user = service.getUserByUserName(username);
        if (null == user) {
   
            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
        }
        if (!BPwdEncoderUtil.matches(password, user.getPassword())) {
   
            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
        }
        //2:使用restTemplate发送请求到授权服务器,申请令牌
        //请求头 "basic auth"
        String client_secret = oAuth2ClientProperties.getClientId() + ":" + oAuth2ClientProperties.getClientSecret();
        client_secret = "Basic " + Base64.getEncoder().encodeToString(client_secret.getBytes());
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", client_secret);

        //请求参数
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.put("username", Collections.singletonList(username));
        map.put("password", Collections.singletonList(password));
        map.put("grant_type", Collections.singletonList(oAuth2ProtectedResourceDetails.getGrantType()));
        map.put("scope",oAuth2ProtectedResourceDetails.getScope());

        //HttpEntity(请求参数,头。。。)
        HttpEntity httpEntity = new HttpEntity(map, headers);

        return restTemplate.exchange(oAuth2ProtectedResourceDetails.getAccessTokenUri(), HttpMethod.POST, httpEntity, OAuth2AccessToken.class);

    }

    @ApiOperation("通过登录获得用户")
    @GetMapping("/get/{userName}")
    public User getByUserName(@PathVariable("userName") String userName) {
   
        return service.getUserByUserName(userName);
    }

    @ApiOperation("通过用户ID获得角色")
    @GetMapping("/select-roles/{id}")
    public List<Role> selectRolesByUserId(@PathVariable("id") Long id) {
   
        return service.selectRoleByUser(id);
    }

    /**
     * 验证用户名是否存在
     */
    @ApiOperation("验证用户名是否存在")
    @PostMapping("/validate-name/{userName}")
    public String validUserName(@PathVariable String userName,  Long id) {
   
        long rowCount = service.findCountByUserName(userName);

        //修改时=原来的用户名
        if (id != null) {
   
            User user = service.getById(id);
            if (null != userName && userName.equals(user.getUserName())) {
   
                return "{\"success\": true}";
            }
        }

        if (rowCount > 0) {
   
            return "{\"success\": false}";
        } else {
   
            return "{\"success\": true}";
        }
    }

    /**
     * 锁定用户
     */
    @GetMapping("/lock/{id}")
    @ApiOperation("锁定账户")
    public ResponseBean lock(@PathVariable Long id) throws Exception {
   
        ResponseBean rm = new ResponseBean();
        try {
   
            User u = service.getById(id);

            User user = new User();
            user.setId(id);
            if (null != u.getLock() && u.getLock()) {
   
                rm.setMsg("用户已解锁");
                user.setLock(false);
            } else {
   
                rm.setMsg("用户已锁定");
                user.setLock(true);
            }
            service.updateById(user);
        } catch (Exception e) {
   
            e.printStackTrace();
            rm.setSuccess(false);
            rm.setMsg("保存失败");
        }

        return rm;
    }

    @Override
    public void afterEdit(User domain) {
   
        //生成角色列表, 如:1,3,4
        List<Role> roles = service.selectRoleByUser(domain.getId());
        Long[] ids = new Long[roles.size()];
        for (int i=0; i< roles.size(); i++) {
   
            ids[i] = roles.get(i).getId();
        }
        domain.setRoleIds(ids);
    }

}

7.6 测试登录

使用postman测试:

Post请求:http://localhost:9002/user/login

1562489532494

9 前端认证授权实现

商城后台管理项目认证和授权基本逻辑如下:

  • 登录时输入用户名、密码参数访问认证服务器
  • 登录成功后,认证服务器返回的token,存储在sessionStorate中
  • 访问其他微服务(如用户,商品,订单,支付微服务),使用Header写到token到这些资源服务器,格式:Bearer token

逻辑图如下:

我们本着轻前端原则,不用重新编写,明白逻辑,能够出现问题,修改就可以,有兴趣的同学可以参考标准代码,自行实现

9.1 登录

src/view/login/login.vue

9.2 路由前置全局守卫

这个前端组件有点像springmvc的前置拦截器。官方文档的解释:

正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。

记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

src/router/index.js

9.3 HTTP协议数据分析

9.3.1 登录

使用Fiddler查看登录HTTP协议数据如下:

9.3.2 访问用户微服务

使用Fiddler查看访问用户微服务的HTTP协议数据如下:

目录
相关文章
|
3天前
|
安全 NoSQL Java
java电商项目(十)
本文介绍了电商系统中订单结算和下单流程的实现。主要包括: 1. **订单结页**: - **收件地址分析**:用户从购物车页面跳转到订单结算页,加载用户收件地址。地址信息存储在 `address_` 表中。 - **实现用户收件地址查询**:通过用户登录名查询收件地址,涉及实体类、DAO、Service 和 Controller 的实现。 2. **下单**: - **业务分析**:下单时创建订单数据,包括订单表 `order_` 和订单明细表 `order_item_`,同时修改商品库存、增加用户积分并删除购物车数据。
14 3
|
3天前
|
消息中间件 安全 Java
java电商项目(十一)
本文接续前几个文章的项目进行讲解!
9 1
|
3天前
|
缓存 NoSQL Java
java电商项目(十二)
本文接续前几个文章的项目进行讲解
13 1
|
3天前
|
存储 NoSQL Java
java电商项目(九)
本文介绍了购物车功能的实现过程,包括用户登录后将商品添加至购物车、购物车列表展示及微服务之间的认证机制。具体步骤如下: 1. **购物车功能**: - 用户选择商品并点击“加入购物车”,系统将商品信息存储到Redis中。 2. **微服务之间认证**: - **传递管理员令牌**:在授权中心微服务调用用户微服务时,生成管理员令牌并通过Header传递。 - **传递当前用户令牌**:用户登录后,通过Feign拦截器将用户令牌传递到其他微服务。 - **获取用户数据**:通过`SecurityContextHolder`获取用户信息,并使用公钥解密令牌以验证用户
10 1
|
3天前
|
监控 算法 Java
java电商项目(七)
微服务网关作为系统唯一对外的入口,位于客户端和服务端之间,处理非业务功能,如路由请求、鉴权、监控、缓存、限流等。它解决了客户端直接调用多个微服务带来的复杂性、跨域请求、认证复杂、难以重构等问题。常用的微服务网关技术有Nginx、Zuul和Spring Cloud Gateway。Spring Cloud Gateway因其集成断路器、路径重写和较好的性能而被广泛使用。本文介绍了如何使用Spring Cloud Gateway搭建后台网关系统,包括引入依赖、配置文件、跨域配置、路由过滤配置、负载均衡、限流等。此外,还详细讲解了RBAC权限数据管理、组织机构管理单点登录(SSO)及JWT鉴权的实现
8 1
|
3天前
|
canal 监控 JavaScript
java电商项目(六)
Thymeleaf 是一个类似于 FreeMarker 的模板引擎,能够完全替代 JSP。它支持动静结合,无网络时显示静态内容,有网络时用后台数据替换静态内容,并且与 Spring Boot 完美整合。本文介绍了如何使用 Thymeleaf 生成商品详情页的静态页面。具体步骤包括创建商品静态化微服务、配置项目依赖、创建 Controller 和 Service、生成静态页面、模板填充、静态资源过滤以及启动测试。此外,还介绍了如何通过 Canal 监听商品数据变化,自动触发静态页面的生成或删除。
9 1
|
3天前
|
SQL 自然语言处理 Java
java电商项目(五)
本文介绍了如何构建一个基于Elasticsearch的商品搜索微服务,主要包括以下几个部分: 1. **数据导入ES**: - 搭建搜索工程,创建`legou-search`项目,提供搜索服务和索引数据更新操作。 - 配置`pom.xml`文件,引入必要的依赖。 - 创建启动器和配置文件,配置Elasticsearch连接信息。 - 分析索引库数据格式,确定需要存储的数据字段。 - 实现商品微服务接口,调用其他微服务获取所需数据。 - 创建索引并导入数据,将SPU和SKU数据转换为索引库中的Goods对象。 2. **商品搜索*
11 1
|
3天前
|
canal NoSQL 关系型数据库
java电商项目(四)
本章介绍了如何通过Lua、OpenResty、Nginx限流及Canal的使用,实现电商门户首页的高并发解决方案。主要内容包括: 1. **商城门户搭建**:使用Vue和iView构建前端门户项目,介绍如何展示商品分类和广告数据,并通过Redis缓存提升访问速度。 2. **Lua基础**:介绍Lua的基本概念、特性、应用场景及安装步骤,并通过示例展示了Lua的基本语法和常用功能。 3. **OpenResty介绍**:详细说明OpenResty的特性和优势,包括如何安装OpenResty和配置Nginx,以及如何使用Lua脚本操作Nginx缓存和数据库。
8 1
|
3天前
|
存储 前端开发 JavaScript
java电商项目(二)
本文档详细介绍了商品分类和规格参数的实现过程。商品分类分为三级管理,主要用于首页商品导航和后台商品管理,采用树状结构存储。规格参数则用于描述商品的具体属性,包括SPU和SKU的定义,规格参数与分类绑定,支持搜索过滤。文档涵盖了表结构设计、实体类、持久层、业务层、控制层的实现,并提供了前端组件的示例代码,确保前后端无缝对接。
11 1
|
3天前
|
前端开发 算法 JavaScript
java电商项目(三)
本文介绍了乐购商城的商品数据分析和管理功能。首先解释了SPU(标准产品单位)和SKU(库存量单位)的概念,以及它们在商品管理和销售中的作用。接着详细分析了SPU、SPU详情和SKU三个表的结构及其关系。文章还介绍了商品管理的需求分析、实现思路和后台代码,包括实体类、持久层、业务层和控制层的实现。最后,文章讲解了前端组件的设计和实现,包括列表组件、添加修改组件、商品描述、通用规格、SKU特有规格和SKU列表的处理。通过这些内容,读者可以全面了解乐购商城的商品管理和数据分析系统。
8 0