单点登录学习

概述

单点登录,英文是 Single Sign On(缩写为 SSO)。就是多个站点公用一台认证服务器,比如下图我刚回答了个问题,在写一篇博客,是不需要再次登录的;而且各站点可以通过该登录状态实现交互。
在这里插入图片描述

**注意:登录是一个独立的系统如下图:**不管在系统1还是系统2登录,他们都会去调用认证授权,其目的就是为了保护数据安全性,判断用户的合法性!
在这里插入图片描述

快速入门

  1. 单点登陆系统解决方案设计
    本次项目中用到的技术有①JWT ②SpringSecurity安全框架 ③OAuth2
    在这里插入图片描述
    在这里插入图片描述

JWT解释
注:JWT中不会存储用户密码,一般存储权限等
在这里插入图片描述

  1. 创建父工程sso 修改pom和配置文件并且在父工程中定义版本
    在这里插入图片描述

1.父工程maven

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jt</groupId>
    <artifactId>02-sso</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>sso-system</module>
    </modules>

    <!--父工程中定义了版本,子工程中则不需要在定义了-->
    <dependencyManagement>
        <dependencies>
            <!--Spring Boot-->
            <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-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR9</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--Spring CloudAlibaba-->
            <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>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope><!--provided表示只提供编译服务-->
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope><!--test表示测试类只能写到maven工程的test目录下-->
            <!--排除junit4 的测试引擎-->
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

        <!--定义统一编译版本-->
    <build>
        <plugins>
            <plugin><!--maven的编译插件-->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target><!--运行的时候指定jdk8-->
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>


1.2在创建一个子工程sso-system继承父工程

<?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>02-sso</artifactId>
        <groupId>com.jt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>sso-system</artifactId>
    <dependencies>
        <!--mysql-->
        <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 discover-->
        <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>
        <!--spring boot web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

</project>

编写配置文件bootstrap.yml

server:
  port: 8061
spring:
  application:
    name: sso-system
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        file-extension: yml
      discovery:
        server-addr: localhost:8848
  datasource:
    url: jdbc:mysql:///jt-sso?serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: root
    password: root
    
logging:
  level:
    com.jt: debug

然后测试一下数据控连接
在这里插入图片描述在这里插入图片描述创建实体类User
在这里插入图片描述
创建mapper实现基于用户名称查询用户信息和基于用户id查询用户权限
在这里插入图片描述

1.3统一认证工程auth

统一认证工程的设计及实现
目的:用户登录时,调用此工程对用户身份进行核验,并授权
在这里插入图片描述
pom依赖

<?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>02-sso</artifactId>
        <groupId>com.jt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>sso-auth</artifactId>

    <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-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!--SpringSecurity+JWT+oauth2对登录的用户进行身份校验,颁发令牌-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
    </dependencies>

</project>

配置文件

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

启动类
**加粗样式**
启动类启动成功后会在控制台生成一个秘钥,然后访问端口号进去一个SpringSecurity进入一个页面,user为底层创建的用户,秘钥控制台生成,登录以后404则代表登录成功,因为没有要跳转的页面,会出现404异常
在这里插入图片描述
在这里插入图片描述
定义用户信息处理对象
定义User:用于封装sso-system工程去查询到的用户信息
在这里插入图片描述定义远程service对象,用于实现远程用户信息调用
在这里插入图片描述
定义用户登陆业务逻辑处理对象

package com.jt.auth.service.impl;

import com.jt.auth.service.RemoteUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
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.stereotype.Service;
import java.util.List;

/**
 * 我们的项目在完成用户身份认证时,
 * 底层会通过UserDetailsService接口的实现类获取用户信息
 *              (默认会去内存中,也可以从数据库或者远端服务获取)
 */
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    /**
     * 远程服务(sso-system)调用接口
     */
    @Autowired
    private RemoteUserService remoteUserService;

    /**
     * 基于用户名获取用户和用户权限信息并封装
     * @param username 这个用户名是用户端的输入
     * @return  User数据和User权限
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1.基于用户名获取远端(现在这里指system服务)用户信息
        com.jt.auth.pojo.User user = remoteUserService.listUserByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        //2.基于远端用户id获取用户权限id
        List<String> userPermissions =
                remoteUserService.listUserByUserIdPermissions(user.getId());

        //3.封装用户信息并返回,交给认证管理器(AuthenticationManager)对用户身份进行认证
        return new User(username,
                user.getPassword(),
                AuthorityUtils.createAuthorityList(
                        userPermissions.toArray(new String[]{})));
        //AuthenticationManager这个接口底层已经提供好认证方法,这里只用给他提供数据即可
    }
}

定义Security配置类

package com.jt.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 因为用户端输入密码以后,要与数据库查询出来的密码进行比对,
     * 所以这里要提供一个密码加密对象,对用户端输入的密码进行加密然后在比对
     * BCryptPasswordEncoder内置了一种不可逆的加密算法
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * AuthenticationManager主要服务于OAuth2的配置
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

定义Oauth2认证授权配置

package com.jt.auth.config;

import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

/**
 * OAuth2是一种协议或者说是一种规范,他定义了完成用户身份认证和授权的一种方式
 * 业务逻辑:
 * 1.认证客户端对象(例如表单,也可以是令牌(QQ或微信等第三方令牌))
 * 2.拿到数据要去认证管理服务器
 * 3.认证管理服务器要让system服务拿到数据,
 * 然后对客户端提交过来的数据和system取数据库拿到的数据进行比较
 * 4.认证成功后颁发令牌
 */
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
    /**
     * 用户端需要携带什么资源进行认证
     * @param clients
     * @throws Exception
     */
    private BCryptPasswordEncoder passwordEncoder;
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()//把规则写到内存里
                .withClient("gateway-client")//定义客户端的标识(客户端到认证服务地址去认证时,需要携带这个信息)
                .secret(passwordEncoder.encode("123456"))//定义客户端携带的秘钥
                .authorizedGrantTypes("password", "refresh_token")//定义授权的类型,password基于密码进行认证,refresh_token基于刷新令牌认证
                .scopes("all");//满足以上条件的客户端都可以认证
    }
    
    /**
     * 去哪里认证(用户需要携带信息去这个你指定的地址认证)
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")//公开,定义这个认证地址(/oauth/token)
                // 用户携带资料到这个地址认证,permitAll()允许所有的到这里认证
                .checkTokenAccess("permitAll()")//公开校验token的地址(/oauth/check_token)
                .allowFormAuthenticationForClients();//允许form表单的认证方式
    }
    
    /**
     * 定义认证的对象(谁来帮你完成认证?),认证成功后颁发什么类型的令牌
     * @param endpoints
     * @throws Exception
     */
    private AuthenticationManager authenticationManager;
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)//指定这个认证管理器对象完成认证
                .tokenServices(tokenServices());//定义令牌服务(有默认令牌服务,但默认令牌服务不满足我们的需求)
    }

    private TokenStore tokenStore;
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        //1.构建令牌服务对象
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        //2.设置令牌创建及存储
        tokenServices.setTokenStore(tokenStore);
        //3.设置令牌增强器(默认是uuid格式的令牌)
        tokenServices.setTokenEnhancer(jwtAccessTokenConverter);
        //4.设置刷新令牌(服务端要创建一个刷新令牌)
        tokenServices.setSupportRefreshToken(true);
        //5.设置访问令牌,设置令牌的有效期
        tokenServices.setAccessTokenValiditySeconds(3600);
        //6.设置刷新令牌
        tokenServices.setRefreshTokenValiditySeconds(5400);
        return tokenServices;
    }
}


但是认证完后需要颁发令牌,默认的不满足我们要求,需要我们手动配置,此类是给上边Oauth2认证授权配置服务的

在这里插入图片描述

1.4资源服务工程sso-resource

pom依赖

<?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>02-sso</artifactId>
        <groupId>com.jt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>sso-resource</artifactId>

    <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>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

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

        <!--资源服务器添加oauth2依赖的目的不是做认证,而是做授权-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
    </dependencies>
</project>

配置类

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

启动类
****加粗样式****
配置类
在这里插入图片描述
在这里插入图片描述
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 注解描述的方法为一个授权切入点方法,
     * 访问此方法时底层会基于AOP方式进行权限检查,
     * 判断用户token的权限中是否包含hasAuthority中指定的权限
     * @return
     */
    @PreAuthorize("hasAuthority('sys:res:list')")
    @GetMapping
    public String doSelect() {
        return "select resource ok";
    }

    @PreAuthorize("hasAuthority('sys:res:create')")
    @PostMapping
    public String doCreate() {
        return "create resource ok";
    }

    @PreAuthorize("hasAuthority('sys:res:update')")
    @PutMapping
    public String doUpdate() {
        return "update resource ok";
    }

    @PreAuthorize("hasAuthority('sys:res:delete')")
    @DeleteMapping
    public String doDelete() {
        return "delete resource ok";
    }

    /**
     * 登录后访问
     * @return
     */
    @GetMapping("/export")
    public String doExport() {
        return "export resource ok";
    }
}

定义一个匿名就可以访问资源的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 注解描述的方法为一个授权切入点方法,
     * 访问此方法时底层会基于AOP方式进行权限检查,
     * 判断用户token的权限中是否包含hasAuthority中指定的权限
     * @return
     */
    @PreAuthorize("hasAuthority('sys:res:list')")
    @GetMapping
    public String doSelect() {
        return "select resource ok";
    }

    @PreAuthorize("hasAuthority('sys:res:create')")
    @PostMapping
    public String doCreate() {
        return "create resource ok";
    }

    @PreAuthorize("hasAuthority('sys:res:update')")
    @PutMapping
    public String doUpdate() {
        return "update resource ok";
    }

    @PreAuthorize("hasAuthority('sys:res:delete')")
    @DeleteMapping
    public String doDelete() {
        return "delete resource ok";
    }

    /**
     * 登录后访问
     * @return
     */
    @GetMapping("/export")
    public String doExport() {
        return "export resource ok";
    }
}

测试
在这里插入图片描述
**加粗样式**

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值