单点登录项目

单点登陆系统

一:服务设计

基于单点登陆系统中的业务描述,进行初步服务架构设计:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wW3eJ3fL-1645616815907)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/18f2af95bf6c46f4af8e3ae291cd9095-16413803696641.png)]

说明: 服务基于业务进行划分

  • system(系统)服务: 通过连接数据库提供基础数据(例如用户信息,日志信息等)
  • auth(认证服务): 负责完成用户身份的校验,密码的比对
  • **resource(资源服务)😗*代表客户需要操作的资源(业务服务(例如我的订单,我的收藏等等).)
  • gateway(网关服务):API网关是服务访问入口,身份认证,资源访问都通过网关进行资源统一转发,流控等
  • UI(客户端):前后端分离架构设计,前端工程服务基于springboot web服务进行实现。
二:工程结构设计

基于服务的划分,设计工程结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-isXGuxEb-1645616815910)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/4f940e5644794a4abb7275a36993c346-16413808992443.png)]

三:SSO父工程创建
1.创建父工程

第一步:创建父工程:

img
2.父工程pom文件初始配置

第二步:初始化pom文件,添加依赖;

<!--maven父工程的pom文件中一般要定义子模块,
    子工程中所需依赖版本的管理,公共依赖并且父工程的打包方式一般为pom方式-->
   <dependencyManagement>
       <dependencies>
           <!--spring boot 核心依赖版本定义(spring官方定义)-->
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-dependencies</artifactId>
               <version>2.3.2.RELEASE</version>
               <type>pom</type>
               <scope>import</scope>
           </dependency>
           <!--Spring Cloud 微服务规范(由spring官方定义)-->
           <dependency>
               <groupId>org.springframework.cloud</groupId>
               <artifactId>spring-cloud-dependencies</artifactId>
               <version>Hoxton.SR9</version>
               <type>pom</type>
               <scope>import</scope>
           </dependency>
           <!--Spring Cloud alibaba 依赖版本管理 (参考官方说明)-->
           <dependency>
               <groupId>com.alibaba.cloud</groupId>
               <artifactId>spring-cloud-alibaba-dependencies</artifactId>
               <version>2.2.6.RELEASE</version>
               <type>pom</type>
               <scope>import</scope>
           </dependency>
       </dependencies>
 
   </dependencyManagement>
   <!--第二步: 添加子工程的所需要的公共依赖-->
<dependencies>
    <!--lombok 依赖,子工程中假如需要lombok,不需要再引入-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </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>
    <!--第三步: 定义当前工程模块及子工程的的统一编译和运行版本-->
<build><!--项目构建配置,我们基于maven完成项目的编译,测试,打包等操作,
    都是基于pom.xml完成这一列的操作,但是编译和打包的配置都是要写到build元素
    内的,而具体的编译和打包配置,又需要plugin去实现,plugin元素不是必须的,maven
    有默认的plugin配置,常用插件可去本地库进行查看-->
    <plugins>
        <!--通过maven-compiler-plugin插件设置项目
           的统一的jdk编译和运行版本-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>8</source>
                <target>8</target>
            </configuration>
        </plugin>
    </plugins>
</build>
四:system系统服务
1.业务描述

本次设计系统服务(System),主要用于提供基础数据服务,例如日志信息,用户信息等。

2.表结构设计

数据库中表的联系如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qNfiiOZU-1645616815911)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/image-20220105191513029-16413813143317.png)]

img
3.创建系统服务工程

第一步:创建sso-system工程(注意在父工程下面创建)

img

第二步:添加项目依赖

<dependencies>
<!--连接数据库-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
<!--mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.2</version>
    </dependency>
<!--添加nacos注册和配置-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
<!--    添加配置依赖的话:需要将配置名改为:bootstrap.yml-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
<!--添加web依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
        <version>2.3.2.RELEASE</version>
    </dependency>
<!--添加sentinel依赖,如果需要限流-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>

</dependencies>

</project>

第三步:在项目中添加bootstrap.yml文件

  • server:
      port: 8061 #端口号
    spring:
      application:
        name: sso-system #服务名
      cloud:
        nacos:
          discovery:  #注册地址
            server-addr: localhost:8848
          config: #配置地址
            server-addr: localhost:8848
            file-extension: yml
      datasource: #可配置在配置中心 方便后期更改;
        url: jdbc:mysql:///jt-sso?serverTimezone=Asia/Shanghai&characterEncoding=utf8
        username: root
        password: root
    
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E7JmZVrZ-1645616815912)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/image-20220105192253902-164138177525712.png)]

第四步:在项目中添加启动类

  • package com.jt;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class SystemApplication {
        public static void main(String[] args) {
            SpringApplication.run(SystemApplication.class,args);
        }
    }
    
    

第五步:在项目中添加单元测试类,测试数据库连接

  • @SpringBootTest
    public class SpringbootTest {
        /**
         * DataSource是一个数据源标准或者规范,java所有连接池需要基于这个规范进行实现;
         * 项目中添加了starter-jdbc依赖后,系统会自动帮我们引入一个HikariCP连接池
         * 这个连接池有一个HikariDataSource对象就是基于上面的规范,这个对象在springboot启动时
         * ,进行自动配置(DataSourceAutoConfiguration)
         */
        @Autowired
        private DataSource dataSource; //HikariDataSource
    
        @Test
        void testGetConnection() throws SQLException {
            //通过DataSource获取连接时,首先要获取的是连接池HikariPool
            //然后从池中获取连接connectionBag
            //池是使用CopyOnWriteArrayList进行创建存储
            //连接池:可以解决每次连接时三次握手和断开连接的四次挥手,大大提高效率--享元模式;
            //HikariPool--connectionBag
            //这里三个设计模式:双重校验单例模式(连接池),享元模式(共享池中的对象),桥接模式
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
        }
    
    
    }
    
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZP0ozp2S-1645616815912)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/image-20211229184458375-164077469944713.png)]

4. Pojo对象逻辑实现

添加项目User对象,用于封装用户信息。

  • /**
     * 创建pojo对象 基于此对象存储用户信息
     * 记住:
     * java中所有用于存储数据的对象,都建议实现序列化
     * 并且添加一个序列化id.
     * 可以参考:string ,Integer,Arraylist,hashmap.......
     */
    @Data
    @Accessors(chain = true)
    @TableName("tb_users") //假如sql语句自己写,则不需要通过此注解添加注解;
    public class User implements Serializable {
        //生成序列化id
        //如果id不写,反序列化时结构改变时会失败;
        private static final long serialVersionUID = 524959160790169643L;
        private Long id;
        private String username;
        private String password;
        private String status;
    }
    
5.Mapper对象逻辑实现

第一步:创建UserMapper接口,并定义基于用户名查询用户信息,基于用户id查询用户权限信息的方法

  • package com.jt.system.dao;
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.jt.system.pojo.User;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Select;
    import java.util.List;
    
    @Mapper
    public interface UserMapper extends BaseMapper<User> {
        /**
         * 基于用户名获取用户信息
         * @param username
         * @return
         */
        @Select("select id,username,password,status " +
                "from tb_users " +
                "where username=#{username}")
        User selectUserByUsername(String username);
        /**
         * 基于用户id查询用户权限
         * @param userId 用户id
         * @return 用户的权限
         * 涉及到的表:tb_user_roles,tb_role_menus,tb_menus
                    三表联查;
         */
        @Select("select distinct m.permission " +
                "from tb_user_roles ur join tb_role_menus rm on              ur.role_id=rm.role_id" +
                "  join tb_menus m on rm.menu_id=m.id " +
                "where ur.user_id=#{userId}")
        List<String> selectUserPermissions(Long userId);
    }
    

第二步:创建UserMapperTests类,对业务方法做单元测试

  • package com.jt;
    import com.jt.system.pojo.User;
    import com.jt.system.dao.UserMapper;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.util.List;
    
    @SpringBootTest
    public class UserMapperTests {
    
        @Autowired
        private UserMapper userMapper;
    
        @Test
        void testSelectUserByUsername(){
            User user =
            userMapper.selectUserByUsername("admin");
            System.out.println(user);
        }
        @Test
        void testSelectUserPermissions(){
            List<String> permission=
            userMapper.selectUserPermissions(1L);
            System.out.println(permission);
        }
    }
    
6.Service对象逻辑实现

创建UserService接口及实现泪,定义用户及用户权限查询逻辑

第一步:定义service接口:

  • package com.jt.system.service;
    
    import com.jt.system.pojo.User;
    
    import java.util.List;
    
    public interface UserService {
        User selectUserByUsername(String username);
        List<String> selectUserPermissions(Long userId);
    }
    
    

第二步:定义service接口实现类

  • package com.jt.system.service.impl;
    
    import com.jt.system.dao.UserMapper;
    import com.jt.system.pojo.User;
    import com.jt.system.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserMapper userMapper;
        @Override
        public User selectUserByUsername(String username) {
            return userMapper.selectUserByUsername(username);
        }
        @Override
        public List<String> selectUserPermissions(Long userId) {
            return userMapper.selectUserPermissions(userId);
        }
    }
    
    
7. Controller对象逻辑实现
  • package com.jt.system.controller;
    
    import com.jt.system.pojo.User;
    import com.jt.system.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    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.util.List;
    
    @RestController
    @RequestMapping("/user/")
    public class UserController {
        @Autowired
        private UserService userService;
    
        @GetMapping("/login/{username}")
        public User doSelectUserByUsername(
                @PathVariable("username") String username){
            return userService.selectUserByUsername(username);
        }
        @GetMapping("/permission/{userId}")
        public List<String> doSelectUserPermissions(
                @PathVariable("userId") Long userId){
            return userService.selectUserPermissions(userId);
        }
    }
    
    
8. 启动服务进行访问测试

启动sso-system工程服务,打开浏览器分别对用户及用户权限信息的获取进行访问测试

  • 1.基于用户名查询用户信息

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j1Xr11F2-1645616815913)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/5bffa4e4dde8480bb33bd014d203d95e-164138264827813.png)]

  • 2.基于用户id(这里假设用户id为1)查询用户权限

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yFExIbQW-1645616815913)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/4dde3f00d1d349f6bb2656fc9dc59396.png)]

五:auth(认证服务)
1.业务描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bYIyMinn-1645616815913)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIyMTcyMTMz,size_16,color_FFFFFF,t_70.jpeg)]

用户登陆时调用此工程对用户身份进行统一身份认证和授权。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h0vqWER2-1645616815914)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/43666cf7cc3d41a3a125449a6016851b.png)]

2.创建工程

第一步:创建sso-auth工程

img

第二步:打开sso-auth工程中的pom文件,然后添加如下依赖:

  • <dependencies>
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-web</artifactId>
           </dependency>
           <dependency>
               <groupId>com.alibaba.cloud</groupId>
               <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
           </dependency>
           <dependency>
               <groupId>com.alibaba.cloud</groupId>
               <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
           </dependency>
           <!--SSO技术方案:SpringSecurity+JWT+oauth2-->
           <dependency>
               <groupId>org.springframework.cloud</groupId>
               <artifactId>spring-cloud-starter-oauth2</artifactId>
           </dependency>
           <!--open feign-->
           <dependency>
               <groupId>org.springframework.cloud</groupId>
               <artifactId>spring-cloud-starter-openfeign</artifactId>
           </dependency>
     
       </dependencies>
     
    

第三步:在sso-auth工程中创建bootstrap.yml文件

  • server:
      port: 8071
    spring:
      application:
        name: sso-auth
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
          config:
            server-addr: localhost:8848
    
    

第四步 添加项目启动类

  • package com.jt;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    @EnableFeignClients //启动Feign 通过feign方式调用system服务获取数据;
    @SpringBootApplication
    public class AuthApplication {
        public static void main(String[] args) {
            SpringApplication.run(AuthApplication.class, args);
        }
    }
    
3.启动并访问项目

项目启动时,系统会默认生成一个登陆密码:

img

打开浏览器输入http://localhost:8071呈现登陆页面

img
4.定义user对象

第一步:定义User对象,用于封装从数据库查询到的用户信息

  • package com.jt.auth.pojo;
    import lombok.Data;
    import java.io.Serializable;
    @Data
    public class User implements Serializable {
        private static final long serialVersionUID = 4831304712151465443L;
        private Long id;
        private String username;
        private String password;
        private String status;
    }
    
    

第二步:定义远程Service对象,用于实现远程用户信息调用

  • package com.jt.auth.service;
    
    import com.jt.auth.pojo.User;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    import java.util.List;
    //sso-system: 需要访问的服务
    @FeignClient(value = "sso-system", contextId ="remoteUserService" )
    public interface RemoteUserService {
         
        @GetMapping("/user/login/{username}")
        User selectUserByUsername( @PathVariable("username") String username);
    
        @GetMapping("/user/permission/{userId}")
        List<String> selectUserPermissions(@PathVariable("userId") Long userId);
    }
    
    
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q7GgqD2O-1645616815914)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/image-20220105194940833-164138338212016.png)]

第三步:定义用户登陆业务逻辑处理对象

  • /**构建UserDetailsService接口实现类,在此类中基于
     * RemoteUserService接口进行远程服务调用,调用sso-system
     * 获取用户信息;
     */
    @Service
    @Slf4j
    public class UserDetailsServiceImpl implements UserDetailsService {
        @Autowired
        private UserService remoteUserService;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //1.基于用户名获取用户信息;
            com.jt.pojo.User user = remoteUserService.getUserByName(username);
            if (user == null) {
                throw new UsernameNotFoundException("用户不存在");
            }
            //2.根据用户的id 获取用户的权限;
            List<String> permissions = remoteUserService.selectUserPermission(user.getId());
            //输出日志
            log.info("permissions{}"+permissions);
    
            //3.封装用户信息并返回:用户名/密码/权限
           //使用spring下的user类中的API
          //import org.springframework.security.core.userdetails.User;
            User userDetails=new User(username,
                    user.getPassword(),//这个密码是从数据库中获取的,与前端传入的进行对比验证;
                    //将权限转换为list集合
                    AuthorityUtils.createAuthorityList(permissions.toArray(new String[] {})));
            //4.将信息交给spring security的认证中心,进行认证分析;
            return userDetails;
        }
    } 
    
    
5. 定义Security配置类

定义Spring Security配置类,在此类中配置认证规则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rbJJ2et9-1645616815915)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/image-20220105195911397.png)]

  • /**
     * 当我们在执行登录操作时,底层逻辑(了解):
     * 1)Filter(过滤器)
     * 2)AuthenticationManager (认证管理器)
     * 3)AuthenticationProvider(认证服务处理器)
     * 4)UserDetailsService(负责用户信息的获取及封装) ok
     */
    
    
  • package com.jt.auto.config;
    
    
    import org.codehaus.jackson.map.ObjectMapper;
    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.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.HashMap;
    import java.util.Map;
    
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        //此对象对密码进行加密;此对象提供了一种不可逆的加密方式,相对于md5方式会更加安全
        @Bean //多次调用,只创建一个对象;
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
       /**
         * 定义认证管理器对象,这个对象负责完成用户信息的认证,
         * 即判定用户身份信息的合法性,在基于oauth2协议完成认
         * 证时,需要此对象,所以这里讲此对象拿出来交给spring管理
         * @return
         * @throws Exception
         */
        @bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        /**此方法为http请求配置方法,可以在此方法中配置;
         * 1.哪些资源放行(不登录可以访问),假如不做配置则默认所有资源都可以访问
         * 2.哪些资源必须认证(登录)之后才可以访问
         * @param
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //super.configure(auth);
           //1.设置禁用跨域攻击:假如没有设置那么所有的第三方工具登录测试都会产生403无权限异常;
                      //只是为了测试 后面完成可以取消;
            http.csrf().disable();
          //  authorizeRequests 相当于拦截器
            //2.所有的资源都需要认证登录的设置;  匹配器            证实
            http.authorizeRequests().antMatchers("/**").authenticated();
            //3.设置所有资源都放行,无需认证登录;
            http.authorizeRequests().anyRequest().permitAll();
    
            //4.配置需要认证的其他的都放行
                //需求:对于default.html需要认证,其余资源无需认证;
            http.authorizeRequests().
                    antMatchers("/default.html").authenticated()
                    .anyRequest().permitAll();
            //5.登录配置(去哪里认证,认证成功或失败的处理器是谁)
            http.formLogin().defaultSuccessUrl("/index.html");
    
            //6.前后端分离的一种做法是登录成功要返回json数据
           //这句话会对外暴露一个登录路径/login
            http.formLogin(). //实现表达提交的认证
              successHandler(successHandler())
                    .failureHandler(failureHandler());
    
        }
    
        /**登录成功处理器*/
        @Bean
        public AuthenticationSuccessHandler successHandler(){
            return new AuthenticationSuccessHandler() {
                @Override
                public void onAuthenticationSuccess(HttpServletRequest Request,
                                                    HttpServletResponse Response,
                                                    Authentication authentication) throws IOException, ServletException {
                  //构建map对象封装到要响应到客户端的数据
                    Map<String,Object> map=new HashMap<>();
                    map.put("status", "200");
                    map.put("message", "登录成功");
                  //调用此方法将map转为json格式;
                    writeJsonToClient(Response, map);
                }
            };
        }
          /**登陆失败处理器*/
          @Bean
          public AuthenticationFailureHandler failureHandler() {
              return new AuthenticationFailureHandler() {
                  @Override
                  public void onAuthenticationFailure(
                          HttpServletRequest request,
                          HttpServletResponse response,
                          AuthenticationException e) throws IOException, ServletException {
                    //构建map对象封装到要响应到客户端的数据
                      Map<String, Object> map = new HashMap<>();
                      map.put("status", 500);
                      map.put("message", "login failure");
                      writeJsonToClient(response, map);
                  }
              };
    
          }
      //自定义一个方法,进行格式转换;
          private void writeJsonToClient(
                HttpServletResponse response,
                Map<String,Object> map) throws IOException {
               //设置响应数据的编码方式
               response.setCharacterEncoding("utf-8");
               //设置响应数据的类型
               response.setContentType("application/json;charset=utf-8");
               //创建输出流对象,将数据转换成json格式输出到前端页面;
               PrintWriter writer=response.getWriter();
               String jsonStr = new ObjectMapper().writeValueAsString(map);
               writer.println(jsonStr);
               writer.flush();
    
          }
    }
    

基于Postman进行访问测试

启动sso-system,sso-auth服务,然后基于postman访问网关,执行登录测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AM2ns9m2-1645616815915)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/6d21271fbe3042dda0220524dbac4690.png)]

6.构建令牌生成及配置对象
  • 借助JWT(Json Web Token-是一种json格式)方式将用户相关信息进行组织和加密,并作为响应令牌(Token),从服务端响应到客户端,客户端接收到这个JWT令牌之后,将其保存在客户端(例如localStorage),然后携带令牌访问资源服务器,资源服务器获取并解析令牌的合法性,基于解析结果判定是否允许用户访问资源.

  • package com.jt.auth.config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    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;
    
    
    /**
     * 在此配置类中配置令牌的生成,存储策略,验签方式(令牌合法性)。
     */
    @Configuration
    public class TokenConfig {
    
        /**
         * 配置令牌的存储策略,对于oauth2规范中提供了这样的几种策略
         * 1)JdbcTokenStore(这里是要将token存储到关系型数据库)
         * 2)RedisTokenStore(这是要将token存储到redis数据库-key/value)
         * 3)JwtTokenStore(这里是将产生的token信息存储客户端,并且token
         * 中可以以自包含的形式存储一些用户信息)
         * 4)....
         */
        @Bean
        public TokenStore tokenStore(){
            //这里采用JWT方式生成和存储令牌信息
            return new JwtTokenStore(jwtAccessTokenConverter());
        }
        /**
         * 配置令牌的创建及验签方式
         * 基于此对象创建的令牌信息会封装到OAuth2AccessToken类型的对象中
         * 然后再存储到TokenStore对象,外界需要时,会从tokenStore进行获取。
         */
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter(){
            JwtAccessTokenConverter jwtAccessTokenConverter=
                    new JwtAccessTokenConverter();
            //JWT令牌构成:header(签名算法,令牌类型),payload(数据部分),Signing(签名)
            //这里的签名可以简单理解为加密,加密时会使用header中算法以及我们自己提供的密钥,
            //这里加密的目的是为了防止令牌被篡改。(这里密钥要保管好,要存储在服务端)
            jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//设置密钥
            return jwtAccessTokenConverter;
        }
    
        /**
         * JWT 令牌签名时使用的密钥(可以理解为盐值加密中的盐)
         * 1)生成的令牌需要这个密钥进行签名
         * 2)获取的令牌需要使用这个密钥进行验签(校验令牌合法性,是否被篡改过)
         */
        private static final String SIGNING_KEY="auth";
    }
    
    
7.定义Oauth2认证授权配置

不是所有配置类都可以作为OAuth2.0认证中心的配置类,需要满足以下两点:

  1. 继承AuthorizationServerConfigurerAdapter
  2. 标注 @EnableAuthorizationServer 注解

AuthorizationServerConfigurerAdapter需要实现的三个方法如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4D3C5Whf-1645616815915)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/image-20220109130158718-164170452014867.png)]

1. 客户端配置

第一步:向认证中心申请几个参数:请求url:

  • /oauth/authorize?client_id=&response_type=code&scope=&redirect_uri=
    
    • client_id:客户端唯一id,认证中心颁发的唯一标识

    • client_secret:客户端的秘钥,相当于密码

    • scope:客户端的权限

    • redirect_uri:授权码模式使用的跳转uri,需要事先告知认证中心。

    •  clients.inMemory()
                      //客户端标识 (id)
                      .withClient("gateway-client")
                      //客户端密钥(随意)
                      .secret(passwordEncoder.encode("123456"))
                      //指定认证类型:密码模式(码密,刷新令牌,三方令牌,...)
                    edGrantTypes("password","refresh_token")
                      //作用域(在这里可以理解为只要包含我们规定信息的客户端都可以进行认证)
                      .scopes("all");
          }
      
  • authorizedGrantTypes():定义认证中心支持的授权类型,总共支持五种

    • 授权码模式:authorization_code
    • 密码模式:password
    • 客户端模式:client_credentials
    • 简化模式:implicit
    • 令牌刷新:refresh_token,这并不是OAuth2的模式,定义这个表示认证中心支持令牌刷新
2. 令牌访问安全约束配置

主要对一些端点的权限进行配置,代码如下:

  •  //super.configure(security);
            //对外发布认证入口(/oauth/token),认证通过服务端会生成一个令牌
            security.tokenKeyAccess("permitAll()")
            //对外发布检查令牌的入口(/oauth/check_token)
            .checkTokenAccess("permitAll()")
            //允许用户通过表单方式提交认证,完成认证
            .allowFormAuthenticationForClients();
        }
    
3.令牌访问端点的配置
  • 配置了密码模式所需要的AuthenticationManager
  • 配置了令牌管理服务,AuthorizationServerTokenServices
  • 配置/oauth/token申请令牌的uri只允许POST提交。

spring Security框架默认的访问端点有如下6个:

  • /oauth/authorize:获取授权码的端点

  • /oauth/token:获取令牌端点。

  • /oauth/confifirm_access:用户确认授权提交端点。

  • /oauth/error:授权服务错误信息端点。

  • /oauth/check_token:用于资源服务访问的令牌解析端点。

  • /oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话。

第二步:客户端拿到授权码之后,直接携带授权码发送请求给认证中心获取令牌,请求的url如下:

  • /oauth/token?
     client_id=&
     client_secret=&
     grant_type=authorization_code&
     code=NMoj5y&
     redirect_uri=
    
  • grant_type:授权类型,授权码固定的值为authorization_code

  • code:这个就是上一步获取的授权码

第三步: 返回令牌

认证中心收到令牌请求之后,通过之后,会返回一段JSON数据,其中包含了令牌access_token,如下:

  • 
    {    
      "access_token":"ACCESS_TOKEN",
      "token_type":"bearer",
      "expires_in":2592000,
      "refresh_token":"REFRESH_TOKEN",
      "scope":"read",
      "uid":100101
    }
    
  • access_token则是颁发的令牌,refresh_token是刷新令牌,一旦令牌失效则携带这个令牌进行刷新。

所有零件准备好了开始拼装最后的主体部分,这个主体部分就是授权服务器的核心配置.

  • package com.jt.auth.config;
    import lombok.AllArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    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.AuthorizationServerTokenServices;
    import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
    import org.springframework.security.oauth2.provider.token.TokenEnhancer;
    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 java.util.UUID;
    
    /**
     * Oauth2 是一种认证授权规范,它基于认证和授权定义了一套规则,在这套规则中规定了
     * 实现一套认证授权系统需要哪些对象:
     * 1)系统资源(数据)
     * 2)资源拥有者(用户)
     * 3)管理资源的服务器
     * 4)对用户进行认证和授权的服务器
     * 5)客户端系统(负责提交用户身份信息的系统)
     *
     * 思考:对于一个认证授权系统来讲,需要什么?:
     * 1)提供一个认证的入口?(客户端去哪里认证)
     * 2)客户端应该携带什么信息去认证?(username,password,....)
     * 3)服务端通过谁去对客户端进行认证(一个负责认证的对象)?
     */
    @AllArgsConstructor
    @Configuration
    @EnableAuthorizationServer //在oauth2规范中启动认证和授权
    public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
        //@Autowired
        private AuthenticationManager authenticationManager;
        //@Autowired
        private BCryptPasswordEncoder passwordEncoder;
        //@Autowired
        private JwtAccessTokenConverter jwtAccessTokenConverter;
        //@Autowired
        private TokenStore tokenStore;
    
        //提供一个认证的入口(客户端去哪里认证)?(http://ip:port/.....)
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            //super.configure(security);
            //对外发布认证入口(/oauth/token),认证通过服务端会生成一个令牌
            security.tokenKeyAccess("permitAll()")
            //对外发布检查令牌的入口(/oauth/check_token)
            .checkTokenAccess("permitAll()")
            //允许用户通过表单方式提交认证,完成认证
            .allowFormAuthenticationForClients();
        }
        //定义客户端应该携带什么信息去认证?
        //指明哪些对象可以到这里进行认证(哪个客户端对象需要什么特点)。
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            //super.configure(clients);
            clients.inMemory()
                    //客户端标识
                    .withClient("gateway-client")
                    //客户端密钥(随意)
                    .secret(passwordEncoder.encode("123456"))
                    //指定认证类型(码密,刷新令牌,三方令牌,...)
                  edGrantTypes("password","refresh_token")
                    //作用域(在这里可以理解为只要包含我们规定信息的客户端都可以进行认证)
                    .scopes("all");
        }
        //提供一个负责认证授权的对象?(完成客户端认证后会颁发令牌,默认令牌格式是uuid方式的)
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            //super.configure(endpoints);
            //设置认证授权对象
            endpoints.authenticationManager(authenticationManager)
            //设置令牌业务对象(此对象提供令牌创建及有效机制设置)
            .tokenServices(tokenService())//不写,默认是uuid
            //设置允许对哪些请求方式进行认证(默认支支持post):可选
            .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
        }
    
        @Bean
        public AuthorizationServerTokenServices tokenService(){
            //1.构建token业务对象
            DefaultTokenServices ts=new DefaultTokenServices();
            //2.设置令牌生成机制(创建6牌的方式,存储用户状态信息的方式)
            ts.setTokenStore(tokenStore);
            //3.设置令牌增强(改变默认令牌创建方式,没有这句话默认是UUID)
            ts.setTokenEnhancer(jwtAccessTokenConverter);
            //4.设置令牌有效时长(可选)
            ts.setAccessTokenValiditySeconds(3600);
            //5.设置刷新令牌以及它的有效时时长(可选)
            ts.setSupportRefreshToken(true);
            ts.setRefreshTokenValiditySeconds(3600*24);
            return ts;
        }
    
    }
    
    

启动postman进行访问测试

  • 登陆访问测试

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pIqSJ2NS-1645616815916)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/8c8001d2e8da4af98e7632bbd9013e45-164139005366218.png)]

  • 检查token信息

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JkIscRWt-1645616815916)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/1cd2eb58c1f64b21a4d1b5e9b375a002-164139007725120.png)]

  • 假如,请求访问ok,在postman控制台会显示如下格式信息

  • {
        "user_name": "admin",
        "scope": [
            "all"
        ],
        "active": true,
        "exp": 1635680023,
        "authorities": [
            "sys:res:create",
            "sys:res:list",
            "sys:res:delete"
        ],
        "jti": "ce4aaee8-031f-4ff8-a0fe-e0cd93e8f374",
        "client_id": "gateway-client"
    }
    
    
  • 刷新令牌应用测试

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-puGpCRd3-1645616815917)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/512da92c2555432db80438d78fed6d62-164139011291822.png)]

六:resource(资源服务)
1.业务描述
  • 资源服务工程为一个业务数据工程,此工程中数据在访问通常情况下是受限访问,例如有些资源有用户,都可以方法,有些资源必须认证才可访问,有些资源认证后,有权限才可以访问。
2.业务设计架构

用户访问资源时的认证,授权流程设计如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5mKqo6Xg-1645616815917)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/fc8aae0e7bd64efc93673a9ebce14221-164139023166424.png)]

3.项目创建

第一步:创建工程

img

第二步:初始化pom文件依赖

  • <dependencies>
         <!--spring boot web-->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <!--nacos discovery-->
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
         </dependency>
         <!--nacos config-->
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
         </dependency>
         <!--sentinel-->
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
         </dependency>
       
         <!--在资源服务器添加此依赖,只做授权,不做认证,添加完此依赖以后,
         在项目中我们要做哪些事情?对受限访问的资源可以先判断是否登录了,
         已经认证用户还要判断是否有权限?
         -->
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-oauth2</artifactId>
         </dependency>
     </dependencies>
       
    

第三步:创建bootstrap.yml配置文

  • server:
      port: 8881
    spring:
      application:
        name: sso-resource
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
          config:
            server-addr: localhost:8848
            file-extension: yml
    
    

第四步:创建启动类

  • package com.jt;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class ResourceApplication {
        public static void main(String[] args) {
            SpringApplication.run(ResourceApplication.class,args);
        }
    }
    
    
4.创建资源Controller对象
  • package com.jt.resource.controller;
    import org.springframework.security.access.prepost.PreAuthorize;
    import org.springframework.web.bind.annotation.*;
    @RestController
    @RequestMapping("/resource")
    public class ResourceController {
    
        /** @PreAuthorize 设置访问时需要验证权限
         * 查询资源
         * @return
         */
        @PreAuthorize("hasAuthority('sys:res:list')")
        @GetMapping
        public String doSelect(){
            return "Select Resource ok";
        }
        /**
         * 创建资源
         * @return
         */
        @PreAuthorize("hasAuthority('sys:res:create')")
        @PostMapping
        public String doCreate(){
            return "Create Resource OK";
        }
        /**
         * 修改资源
         * @return
         */
        @PreAuthorize("hasAuthority('sys:res:update')")
        @PutMapping
        public String doUpdate(){
            return "Update Resource OK";
        }
        /**
         * 删除资源
         * @return
         */
        @DeleteMapping
        public String doDelete(){
            return "Delete resource ok";
        }
    }
    
    
5.配置令牌解析器对象
  • package com.jt;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    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;
    
    
    /**
     * 在此配置类中配置令牌的生成,存储策略,验签方式(令牌合法性)。
     */
    @Configuration
    public class TokenConfig {
    
        /**
         * 配置令牌的存储策略,对于oauth2规范中提供了这样的几种策略
         * 1)JdbcTokenStore(这里是要将token存储到关系型数据库)
         * 2)RedisTokenStore(这是要将token存储到redis数据库-key/value)
         * 3)JwtTokenStore(这里是将产生的token信息存储客户端,并且token
         * 中可以以自包含的形式存储一些用户信息)
         * 4)....
         */
        @Bean
        public TokenStore tokenStore(){
            //这里采用JWT方式生成和存储令牌信息
            return new JwtTokenStore(jwtAccessTokenConverter());
        }
        /**
         * 配置令牌的创建及验签方式
         * 基于此对象创建的令牌信息会封装到OAuth2AccessToken类型的对象中
         * 然后再存储到TokenStore对象,外界需要时,会从tokenStore进行获取。
         */
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter(){
            JwtAccessTokenConverter jwtAccessTokenConverter=
                    new JwtAccessTokenConverter();
            //JWT令牌构成:header(签名算法,令牌类型),payload(数据部分),Signing(签名)
            //这里的签名可以简单理解为加密,加密时会使用header中算法以及我们自己提供的密钥,
            //这里加密的目的是为了防止令牌被篡改。(这里密钥要保管好,要存储在服务端)
            jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//设置密钥
            return jwtAccessTokenConverter;
        }
    
        /**
         * JWT 令牌签名时使用的密钥(可以理解为盐值加密中的盐)
         * 1)生成的令牌需要这个密钥进行签名
         * 2)获取的令牌需要使用这个密钥进行验签(校验令牌合法性,是否被篡改过)
         */
        private static final String SIGNING_KEY="auth";
    }
    
    
6.配置资源认证授权规则
  • package com.jt;
    
    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;
    
    /**
     * 思考?对于一个系统而言,它资源的访问权限你是如何进行分类设计的
     * 1)不需要登录就可以访问(例如12306查票)
     * 2)登录以后才能访问(例如12306的购票)
     * 3)登录以后没有权限也不能访问(例如会员等级不够不让执行一些相关操作)
     */
    @Configuration
    @EnableResourceServer
    //启动方法上的权限控制,需要授权才可访问的方法上添加@PreAuthorize等相关注解
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class ResourceConfig extends ResourceServerConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            //super.configure(http);
            //1.关闭跨域攻击
            http.csrf().disable();
            //2.放行相关请求
            http.authorizeRequests()
                    .antMatchers("/resource/**")
                    .authenticated()
                    .anyRequest().permitAll();
        }
    }
    
    
7.启动Postman进行访问测试

不携带令牌访问

img

携带令牌访问

img

没有访问权限

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-225A04ht-1645616815918)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/image-20220109155222332-164171474350969.png)]

注意:更改没有权限,所以put提交会报403异常;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZo92dGZ-1645616815918)(C:/Users/86131/Desktop/%E5%AD%A6%E4%B9%A0%E5%9B%BE%E5%BA%93/image-20220109155132856-164171469387468.png)]

七:网关服务
1.业务描述
  • 本次设计中,API网关是服务访问入口,身份认证,资源访问都通过网关进行资源统一转发。
2.项目创建及初始化

第一步:创建项目

img

第二步:初始化pom文件内容

  • <dependencies>
    <!--网关服务依赖(底层基于netty技术和webflux)-->
           <dependency>
               <groupId>org.springframework.cloud</groupId>
               <artifactId>spring-cloud-starter-gateway</artifactId>
           </dependency>
    <!--服务的注册-->
           <dependency>
               <groupId>com.alibaba.cloud</groupId>
               <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
           </dependency>
           <!-- 服务的配置-->
           <dependency>
               <groupId>com.alibaba.cloud</groupId>
               <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
           </dependency>
    <!--服务的限流依赖(网关限流依赖有2个)-->
           <dependency>
               <groupId>com.alibaba.cloud</groupId>
               <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
           </dependency>
           <dependency>
               <groupId>com.alibaba.cloud</groupId>
               <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
           </dependency>
       </dependencies>
    
    

第三步:创建bootstrap.yml配置文件并进行路由定义

  • server:
      port: 9000
    spring:
      application:
        name: sso-gateway
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
          config:
            server-addr: localhost:8848
            file-extension: yml
        sentinel:
          transport:
            dashboard: localhost:8180
          eager: true
        gateway:
          routes:
            - id: router01 #资源服务器路由
              uri: lb://sso-resource  #lb表示负载均衡
              predicates:   #谓词对象 可以定义多个谓词逻辑 所有的谓词逻辑返回值为TRUE才会执行filter链
                - Path=/sso/resource/**
              filters:    #过滤器 谓词逻辑的下一个执行步骤
                - StripPrefix=1 #去掉path中的第一层目录
            - id: router02 #认证服务器路由
              uri: lb://sso-auth
              predicates:
                - Path=/sso/oauth/**
              filters:
                - StripPrefix=1
          globalcors: #跨域配置(针对AJAX请求,写到配置文件的好处是可以将其配置写到配置中心)
           corsConfigurations:
            '[/**]':
              allowedOrigins: "*"
              allowedHeaders: "*"
              allowedMethods: "*"
              allowCredentials: true
    
    

第四步:定义启动类

  • package com.jt;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    @SpringBootApplication
    public class ApiGatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(ApiGatewayApplication.class, args);
        }
    }
    
    
    
3.启动postman进行访问测试

基于网关进行登陆访问测试

img

基于网关进行资源访问测试

img

八:UI服务

第一步:在resource目录下创建static目录

第二步:在static目录下创建登陆页面login.html

  • <!doctype html>
    <html lang="en">
    <head>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <!-- Bootstrap CSS -->
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
        <title>login</title>
    </head>
    <body>
    <div class="container"id="app">
        <h3>Please Login</h3>
        <form>
            <div class="mb-3">
                <label for="usernameId" class="form-label">Username</label>
                <input type="text" v-model="username" class="form-control" id="usernameId" aria-describedby="emailHelp">
            </div>
            <div class="mb-3">
                <label for="passwordId" class="form-label">Password</label>
                <input type="password" v-model="password" class="form-control" id="passwordId">
            </div>
            <button type="button" @click="doLogin()" class="btn btn-primary">Submit</button>
        </form>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
        var vm=new Vue({
            el:"#app",//定义监控点,vue底层会基于此监控点在内存中构建dom树
            data:{ //此对象中定义页面上要操作的数据
                username:"",
                password:""
            },
            methods: {//此位置定义所有业务事件处理函数
                doLogin() {
                    //1.定义url
                    let url = "http://localhost:9000/sso/oauth/token"
                    //2.定义参数
                    let params = new URLSearchParams()
                    params.append('username',this.username);
                    params.append('password',this.password);
                    params.append('client_id',"gateway-client");
                    params.append('client_secret',"123456");
                    params.append('grant_type',"password");
                    //3.发送异步请求
                    axios.post(url, params)
                        .then((response) => {//ok
                             alert("login ok")
                             let result=response.data;
                             console.log("result",result);
                             //将返回的访问令牌存储到浏览器本地对象中
                             localStorage.setItem("accessToken",result.access_token);
                             location.href="/resource.html";
                             //启动一个定时器,一个小时以后,向认证中心发送刷新令牌
                         })
                        .catch((e)=>{
                            console.log(e);
                        })
                }
            }
        });
    </script>
    </body>
    </html>
    
    

第三步:打开浏览器进行访问测试

img

1.创建资源展现页面

第一步:在UI工程的static目录下创建resource.html

  • <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <div>
        <h1>The Resource Page</h1>
        <button οnclick="doSelect()">查询我的资源</button>
        <button οnclick="doUpdate()">修改我的资源</button>
    </div>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
        function doSelect(){
            let url="http://localhost:9000/sso/resource";
            //获取登录后,存储到浏览器客户端的访问令牌
            let token=localStorage.getItem("accessToken");
            //发送请求时,携带访问令牌
            axios.get(url,{headers:{"Authorization":"Bearer "+token}})
                .then(function (response){
                    alert("select ok")
                    console.log(response.data);
                })
                .catch(function (e){//失败时执行catch代码块
                    if(e.response.status==401){
                        alert("请先登录");
                        location.href="/login.html";
                    }else if(e.response.status==403){
                        alert("您没有权限")
                    }
                    console.log("error",e);
                })
        }
        function doUpdate(){
            let url="http://localhost:9000/sso/resource";
            //获取登录后,存储到浏览器客户端的访问令牌
            let token=localStorage.getItem("accessToken");
            console.log("token",token);
            //发送请求时,携带访问令牌
            axios.put(url,"",{headers:{"Authorization":"Bearer "+token}})
                .then(function (response){
                    alert("update ok")
                    console.log(response.data);
                })
                .catch(function (e){//失败时执行catch代码块
                    console.log(e);
                    if(e.response.status==401){
                        alert("请先登录");
                        location.href="/login.html";
                    }else if(e.response.status==403){
                        alert("您没有权限")
                    }
                    console.log("error",e);
                })
        }
    </script>
    </body>
    </html>
    
    
    

第二步:打开浏览器进行访问测试(登陆前和登陆后检查点击如下按钮检测结果

img

lStorage.getItem(“accessToken”);
//发送请求时,携带访问令牌
axios.get(url,{headers:{“Authorization”:“Bearer “+token}})
.then(function (response){
alert(“select ok”)
console.log(response.data);
})
.catch(function (e){//失败时执行catch代码块
if(e.response.status401){
alert(“请先登录”);
location.href="/login.html";
}else if(e.response.status
403){
alert(“您没有权限”)
}
console.log(“error”,e);
})
}
function doUpdate(){
let url=“http://localhost:9000/sso/resource”;
//获取登录后,存储到浏览器客户端的访问令牌
let token=localStorage.getItem(“accessToken”);
console.log(“token”,token);
//发送请求时,携带访问令牌
axios.put(url,””,{headers:{“Authorization”:"Bearer "+token}})
.then(function (response){
alert(“update ok”)
console.log(response.data);
})
.catch(function (e){//失败时执行catch代码块
console.log(e);
if(e.response.status401){
alert(“请先登录”);
location.href="/login.html";
}else if(e.response.status
403){
alert(“您没有权限”)
}
console.log(“error”,e);
})
}


第二步:打开浏览器进行访问测试(登陆前和登陆后检查点击如下按钮检测结果

![img](https://i-blog.csdnimg.cn/blog_migrate/4e27ac50564b9c0a3a95dd403edef3a6.png)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值