返回介绍

21 SSO

发布于 2024-07-13 17:49:55 字数 22464 浏览 0 评论 0 收藏 0

  • 单点登录
  • 单点登出
  • 支持跨域单点登录
  • 支持跨域单点登出

前台站点:业务站点A,业务站点B

SSO站点:登录,退出

SSO服务:登录,登录状态,登出

数据库,登录状态缓存在Redis

  • 登录时序图

客户端,访问需要登录的页面,从业务站点,如果未从Cookie中获取AuthToken,跳转到登录页面,访问SSO站点,提交用户名,密码,验证用户登录的 SSO 服务,访问DB验证账号,保存登录状态 Redis,返回成功Redis,返回AuthToken,将AuthToken放入Cookie中 domian = test.com 返回 302:跳转到页面,将AuthToken保存到Cookie中,domain = test.com,跳转到访问的页面

  • 登录完成之后通过回调的方式,将AuthToken传递给主域名之外的站点,该站点自行将AuthToken保存在当前域下的Cookie中。
  • 登出完成之后通过回调的方式,调用非主域名站点的登出页面,完成设置Cookie中的AuthToken过期的操作。

这个错误提示表明,代码尝试从ServletContext中调用一个名为getVirtualServerName()的方法,但是该方法不存在。这可能是由于以下原因之一导致的:

  1. Servlet API版本不兼容 - getVirtualServerName()方法是在Servlet API 3.1中引入的。如果代码是在旧版本的Servlet API下编译的,那么它将无法识别getVirtualServerName()方法。
  2. 应用程序服务器问题 - 如果正在运行应用程序服务器,并且该服务器未按照规范实现ServletContext接口,则可能会遇到此问题。在这种情况下,更新应用程序服务器可能解决问题。

修复问题

  1. 检查代码是否使用了正确版本的Servlet API。如果没有,请升级的Servlet API并重新编译代码。
  2. 确保正在运行符合规范的应用程序服务器。如果已经在使用符合规范的应用程序服务器,请尝试更新它以获取最新的Servlet API实现。
package com.wxapp.app;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
@MapperScan("com.wxapp.app.dao")
@MapperScan("com.wxapp.app.mapper")
public class AdminApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(AdminApplication.class);
        application.setBannerMode(Banner.Mode.OFF);
        application.run(args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        // 注意这里要指向原先用main方法执行的Application启动类
        return builder.sources(AdminApplication.class);
    }

}
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>com.github.qcloudsms</groupId>
    <artifactId>qcloudsms</artifactId>
    <version>1.0.6</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.7</version>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <!--            <version>3.4.3</version>-->
    <version>3.1.0</version>
</dependency>
<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

<!--调用 HTTP 请求-->
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>3.8.1</version>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.6.5</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.6</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.72</version>
</dependency>
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.2.0</version>
</dependency>
@RestController
@RequestMapping("/article")
@Slf4j
public class ArticleController {
    @Autowired
    private ArticleService articleService;

    /**
     * 查询文章列表
     */
    @NoAuth
    @RequiresPermissions("article:list")
    @GetMapping("/listArticle")
    public JSONObject listArticle(HttpServletRequest request) {
        return articleService.listArticle(CommonUtil.request2Json(request));
    }
    /**
     * 新增文章
     */
    @NoAuth
    @RequiresPermissions("article:add")
    @PostMapping("/addArticle")
    public JSONObject addArticle(@RequestBody JSONObject requestJson) {
        CommonUtil.hasAllRequired(requestJson, "content");
        return articleService.addArticle(requestJson);
    }
    /**
     * 修改文章
     */
    @RequiresPermissions("article:update")
    @PostMapping("/updateArticle")
    public JSONObject updateArticle(@RequestBody JSONObject requestJson) {
        CommonUtil.hasAllRequired(requestJson, "id,content");
        return articleService.updateArticle(requestJson);
    }
}
public interface ArticleDao {
    /**
     * 新增文章
     */
    int addArticle(JSONObject jsonObject);
    /**
     * 统计文章总数
     */
    int countArticle(JSONObject jsonObject);
    /**
     * 文章列表
     */
    List<JSONObject> listArticle(JSONObject jsonObject);
    /**
     * 更新文章
     */
    int updateArticle(JSONObject jsonObject);
}
@Service
public class ArticleService {
    @Autowired
    private ArticleDao articleDao;
    /**
     * 新增文章
     */
    @Transactional(rollbackFor = Exception.class)
    public JSONObject addArticle(JSONObject jsonObject) {
        articleDao.addArticle(jsonObject);
        return CommonUtil.successJson();
    }
    /**
     * 文章列表
     */
    public JSONObject listArticle(JSONObject jsonObject) {
        CommonUtil.fillPageParam(jsonObject);
        int count = articleDao.countArticle(jsonObject);
        List<JSONObject> list = articleDao.listArticle(jsonObject);
        return CommonUtil.successPage(jsonObject, list, count);
    }
    /**
     * 更新文章
     */
    @Transactional(rollbackFor = Exception.class)
    public JSONObject updateArticle(JSONObject jsonObject) {
        articleDao.updateArticle(jsonObject);
        return CommonUtil.successJson();
    }
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.wxapp.app.dao.ArticleDao">
    <insert id="addArticle" parameterType="com.alibaba.fastjson.JSONObject">
        INSERT INTO article (content) VALUES (#{content})
    </insert>
    <select id="countArticle" resultType="Integer">
        SELECT COUNT(*) FROM article
    </select>
    <select id="listArticle" resultType="com.alibaba.fastjson.JSONObject">
        SELECT w.id,
               w.content,
               date_format(w.create_time, '%Y.%m.%d %T') createTime,
               date_format(w.update_time, '%Y.%m.%d %T') updateTime
        FROM article w
        ORDER BY w.id DESC
            LIMIT #{offSet}, #{pageRow}
    </select>
    <update id="updateArticle" parameterType="com.alibaba.fastjson.JSONObject">
        UPDATE article
        SET content = #{content}
        WHERE id = #{id}
    </update>
</mapper>

@Service
@Slf4j
public class LoginService {
    @Autowired
    private LoginDao loginDao;
    /**
     * 登录提交
     */
    public JSONObject authLogin(JSONObject jsonObject) {
        String phone = jsonObject.getString("phone");
        String code = jsonObject.getString("code");
        JSONObject info = new JSONObject();
        JSONObject user = loginDao.checkUser(phone, code);
        if (user == null) {
            throw null;
        }
        String successMsg = "登录成功";
        info.put("msg", successMsg);
        return CommonUtil.successJson(info);
    }

    /**
     * 查询当前登录用户的权限等信息
     */
    public JSONObject getInfo(JSONObject jsonObject) {
        //从session获取用户信息
        String phone = jsonObject.getString("phone");
        //SessionUserInfo userInfo = tokenService.getUserInfo();
        SessionUserInfo userInfo = loginDao.getUserInfo(phone);
        log.info(userInfo.toString());
        return CommonUtil.successJson(userInfo);
    }

    /**
     * 推出登录
     */
    public JSONObject logout() {
        JSONObject info = new JSONObject();
        String successMsg = "登出成功";
        info.put("msg", successMsg);
        return CommonUtil.successJson(info);
    }
}
public interface LoginDao {
    JSONObject checkUser(@Param("phone") String phone, @Param("code") String code);
    //Login login(@Param("phone") String phone, @Param("code") String code);

    SessionUserInfo getUserInfo(String phone);

    Set<String> getAllMenu();

    Set<String> getAllPermissionCode();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.wxapp.app.dao.LoginDao">
    <select id="checkUser" resultType="com.alibaba.fastjson.JSONObject">
        SELECT * FROM la_user WHERE phone=#{phone} AND code=#{code}
    </select>

    <resultMap id="userInfo" type="com.wxapp.app.dto.session.SessionUserInfo">
        <id column="id" property="id"/>
        <result column="phone" property="phone"/>
        <result column="username" property="username"/>
        <result column="nickname" property="nickname"/>
        <collection property="roleIds" ofType="Integer">
            <id column="roleId"/>
        </collection>
        <collection property="menuList" ofType="String">
            <id column="menuCode"/>
        </collection>
        <collection property="permissionList" ofType="String">
            <id column="permissionCode"/>
        </collection>
    </resultMap>

    <select id="getUserInfo" resultMap="userInfo">
        SELECT u.id              id,
               u.phone,
               u.username,
               u.nickname,
               ur.role_id        roleId,
               p.menu_code       menuCode,
               p.permission_code permissionCode
        FROM la_user u
              LEFT JOIN sys_user_role ur on u.id = ur.user_id
              LEFT JOIN sys_role r ON r.id = ur.role_id
              LEFT JOIN sys_role_permission rp ON r.id = rp.role_id
              LEFT JOIN sys_permission p ON rp.permission_id = p.id
        WHERE u.phone=#{phone}
    </select>
    <select id="getAllMenu" resultType="String">
        select distinct(menu_code)
        from sys_permission;
    </select>
    <select id="getAllPermissionCode" resultType="String">
        select distinct(permission_code)
        from sys_permission;
    </select>
</mapper>
@RestController
@RequestMapping(value = "/login1")
@Slf4j
public class LoginController {

    @Autowired
    private LoginService loginService;

    /**
     * 登录
     */
    @NoAuth
    @RequestMapping(value = "/auth", method = RequestMethod.POST)
    //@PostMapping("/auth")
    public JSONObject authLogin(@RequestBody JSONObject requestJSON) {
        CommonUtil.hasAllRequired(requestJSON, "phone,code");
        return loginService.authLogin(requestJSON);
    }

    /**
     * 查询当前登录用户的信息
     */
    @NoAuth
    @PostMapping("/getInfo")
    public JSONObject getInfo(@RequestBody JSONObject requestJSON) {
        return loginService.getInfo(requestJSON);
    }

    /**
     * 登出
     */
    @PostMapping("/logout")
    public JSONObject logout() {
        return loginService.logout();
    }
}
package com.wxapp.app.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wxapp.app.pojo.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}
package com.wxapp.app.dto.session;

import lombok.Data;

import java.util.List;
import java.util.Set;

/**
 * 保存在session中的用户信息
 */
@Data
public class SessionUserInfo {
    private int id;
    private String phone;
    private String username;
    private String nickname;
    private List<Integer> roleIds;
    private Set<String> menuList;
    private Set<String> permissionList;
}
package com.wxapp.app.config.annotation;

public enum Logical {
    AND, OR, NOT;
}
package com.wxapp.app.config.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 访问此接口需要的权限
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {
    String[] value();
    Logical logical() default Logical.AND;
}
package com.wxapp.app.common;

import lombok.Data;

@Data
public class Result<T> {

    //操作代码
    Integer code;

    //提示信息
    String message;

    //结果数据
    T data;

    public Result() {
    }

    public Result(ResultCode resultCode) {
        this.code = resultCode.code();
        this.message = resultCode.message();
    }

    public Result(ResultCode resultCode, T data) {
        this.code = resultCode.code();
        this.message = resultCode.message();
        this.data = data;
    }

    public Result(String message) {
        this.message = message;
    }

    public static Result SUCCESS() {
        return new Result(ResultCode.SUCCESS);
    }

    public static <T> Result SUCCESS(T data) {
        return new Result(ResultCode.SUCCESS, data);
    }

    public static Result FAIL() {
        return new Result(ResultCode.FAIL);
    }

    public static Result FAIL(String message) {
        return new Result(message);
    }

}

主要是Error creating bean with name 'sqlSessionFactory',这个是在XXX.xml(spring和mybatis整合的xml文件)中定义

并且出现的,其主要意思是xml文件没有配置好。原因大概有以下:

1.spring和mybatis结合的时候在spring配置bean的配置文件中没有把配置sqlSessionFactory中的dataSource和配置数据库连 连接池的dataSource放在同一个配置文件中;

2.mybatis的xml配置文件没有把mybatis的映射文件和Dao接口相互关联配置在一起;

3.虽然定义了数据库连接池的信息,但是可能没有连接到数据库;

我的解决方案:首先确认了数据库在数据库连接池中的配置并确定了数据库的连接状态完好,其次将有关dataSource有关的mybatis配置以及数据库连接池的配置都放在了同一个spring的xml配置文件中,最后再次需要确定spring、mybatis以及数据库连接池之间的相互关联关系是正常的,需要检查mybatis的xml配置文件和xml映射配置文件中相互映射(xml配置文件中的mapper以及xml映射文件中的namespace)是否正确。

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is java.lang.NoClassDefFoundError: org/mybatis/logging/LoggerFactory

解决方法:

在SpringBootApplication启动类上添加如下注解:

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
datasource:
  druid:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: root
    password: 

service类上面没有@service注解或者mapper上没有@Repository注解,但是这种情况比较少见,一般不会忘记。



package com.wxapp.app;

import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class AdminApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(AdminApplication.class);
        application.setBannerMode(Banner.Mode.OFF);
        application.run(args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        // 注意这里要指向原先用main方法执行的Application启动类
        return builder.sources(AdminApplication.class);
    }

}
  1. 确认 SysLoginService 类被正确地声明和定义,且放置在了 Spring 扫描的包路径下。
  2. 如果 SysLoginService 是通过注解方式来声明的,那么要确保该类上已经添加了正确的注解,例如 @Service@Component
  3. 如果 SysLoginService 类依赖于其它 Bean,请确保这些 Bean 已经正确地被声明和定义,并且也位于 Spring 扫描的包路径下。
  4. 确认在 Spring 配置文件中添加了正确的组件扫描配置,以便 Spring 能够扫描到 SysLoginService 类和其它相关的 Bean。

如果以上步骤都已确认无误,但问题仍然存在,可以考虑使用 Spring 的调试工具来进行进一步的排查。

package com.wxapp.app;

import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class WxAppApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(WxAppApplication.class);
        application.setBannerMode(Banner.Mode.OFF);
        application.run(args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        // 注意这里要指向原先用main方法执行的Application启动类
        return builder.sources(WxAppApplication.class);
    }

}

基于mybatis开发,新功能基于mybatis-plus开发,同时依赖如下两个jar包

mybatis-spring-boot-starter

mybatis-plus-boot-starter

移除mybatis-spring-boot-starter依赖

原配置如下:

mybatis:
    mapperLocations: classpath:mapper/**/*.xml

新配置:

mybatis-plus:
  mapperLocations: classpath:mapper/**/*.xml,classpath:/mybatis-plus/*.xml

主要就是在解决从mybatis切换到mybatis-puls后,使用了BaseMapper的用户mapper接口与XXXmapper.xml文件方法绑定的问题,为使用了BaseMapper的用户mapper接口在内存中生成xml映射文件

关于Springboot+Mybatis——Error creating bean with name 'sqlSessionFactoryBean--Mapper映射问题

1.mybatis中

<!-- Spring-Mybatis -->
<dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.2</version>
        <!--原Mybatis中需排除下面2个依赖-->
        <exclusions>
                <exclusion>
                        <groupId>org.mybatis</groupId>
                        <artifactId>mybatis</artifactId>
                </exclusion>
                <exclusion>
                        <groupId>org.mybatis</groupId>
                        <artifactId>mybatis-spring</artifactId>
                </exclusion>
        </exclusions>
</dependency>
  1. pagehelper 中
<!-- 分页插件 -->
<dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.2.3</version>
        <!--需排除下面包-->
        <exclusions>
                <exclusion>
                        <groupId>org.mybatis.spring.boot</groupId>
                        <artifactId>mybatis-spring-boot-starter</artifactId>
                </exclusion>
        </exclusions>
</dependency>

3.引入[Mybatis-plus]

<!--引入Mybatis-plus-->
<dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.3.2</version>
</dependency>

4.引入autoconfigure

<!--引入autoconfigure-->
<dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
        <version>2.1.4</version>
</dependency>
  1. 修改配置文件,将原 mybatis 改成 mybatis-plus
mybatis-plus:
  mapper-locations: classpath*:/mapper/*.xml,classpath*:/lemon/mapper/*.xml
  configuration:
    mapUnderscoreToCamelCase: true
mybatis-plus:
  mapper-locations: classpath*:/mapper/*.xml,classpath*:/mapper/**/*.xml
  configuration:
    mapUnderscoreToCamelCase: true
SpringBoot启动流程.png

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文