当前位置: 首页 > 资讯 > >正文

基于Sa-Token实现微服务之前的单点登录

来源:博客园    时间:2023-07-02 11:26:54
修改配置文件,准备好四个域名
127.0.0.1  auth.server.com127.0.0.1  user.server.com127.0.0.1  third.server.com127.0.0.1  eureka.server.com
注册中心:eureka-server服务pom依赖
            org.springframework.cloud            spring-cloud-starter-netflix-eureka-server            3.1.1                                    org.springframework.boot            spring-boot-starter-web            ${spring-boot-start-version}                            org.webjars            jquery            3.5.0        
配置web项目启动类
/** * @description: Eureka 服务端注册中心:剔除数据源操作 * @author: GuoTong * @createTime: 2023-06-26 21:50 * @since JDK 1.8 OR 11 **/@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})@EnableEurekaServerpublic class EurekaApplication {    public static void main(String[] args) {        SpringApplication.run(EurekaApplication.class, args);    }}
配置跨域支持和静态资源过滤
/** * @description: SpringBoot-Web配置 * @author: GuoTong * @createTime: 2023-06-05 15:37 * @since JDK 1.8 OR 11 **/@Configurationpublic class SpringBootConfig implements WebMvcConfigurer {    /**     * Description:  添加全局跨域CORS处理     */    @Override    public void addCorsMappings(CorsRegistry registry) {        // 设置允许跨域的路径        registry.addMapping("/**")                //设置允许跨域请求的域名                .allowedOriginPatterns("*")                // 是否允许证书                .allowCredentials(true)                // 设置允许的方法                .allowedMethods("GET", "POST", "DELETE", "PUT")                // 设置允许的header属性                .allowedHeaders("*")                // 跨域允许时间                .maxAge(3600);    }    /**     * Description: 静态资源过滤     */    @Override    public void addResourceHandlers(ResourceHandlerRegistry registry) {        //ClassPath:/Static/** 静态资源释放        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");        //释放swagger        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");        //释放webjars        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");    }}
配置文件基础配置
server:  port: 10086spring:  application:    name: eureka-server  security:    user:      name: eureka      password: eureka  mvc:    static-path-pattern: classpath:/static/**eureka:  client:    service-url:      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka    register-with-eureka: false #自己不向注册中心注册自己    fetch-registry: false  # 自己是注册中心  instance:    hostname: 127.0.0.1    prefer-ip-address: true
启动注册中心验证准备Sa-Token认证中心server

https://sa-token.cc/doc.html#/ 可以自己参考官方网站定制


(资料图)

引入依赖

knife4j / mysql / mybatis / sa-token / commons / thymeleaf / loadbalancer / bootstrap /eureka-client / lombok

            org.projectlombok            lombok                                    com.github.xiaoymin            knife4j-spring-boot-starter            ${knife4j.version}                                    mysql            mysql-connector-java            ${mysql-version}                                    com.baomidou            mybatis-plus-boot-starter            ${mybatisplus.verison}                            com.baomidou            mybatis-plus            ${mybatisplus.verison}                                    cn.dev33            sa-token-spring-boot-starter            1.33.0                                    cn.dev33            sa-token-sso            1.33.0                                    cn.dev33            sa-token-dao-redis-jackson            1.33.0                            org.apache.commons            commons-pool2                                    org.springframework.boot            spring-boot-starter-thymeleaf                                    org.springframework.cloud            spring-cloud-starter-loadbalancer            ${spring-cloud-starter-version}                                    org.springframework.cloud            spring-cloud-starter-bootstrap            ${spring-cloud-starter-version}                            org.springframework.cloud            spring-cloud-starter-netflix-eureka-client            3.1.1        
核心Sa-Token的接口
/** * @description: 统一认证中心 SSO-Server用于对外开放接口: * @author: GuoTong * @createTime: 2022-11-26 23:03 * @since JDK 1.8 OR 11 **/@RestControllerpublic class SsoServerController {    private AuthLoginUserService authLoginUserService;    /**     * Description: 构造器注入     */    public SsoServerController(AuthLoginUserService authLoginUserService) {        this.authLoginUserService = authLoginUserService;    }    /*     * /*     * SSO-Server端:处理所有SSO相关请求     * http://{host}:{port}/sso/auth-- 单点登录授权地址,接受参数:redirect=授权重定向地址     * http://{host}:{port}/sso/doLogin-- 账号密码登录接口,接受参数:name、pwd     * http://{host}:{port}/sso/checkTicket-- Ticket校验接口(isHttp=true时打开),接受参数:ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选]     * http://{host}:{port}/sso/signout-- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、secretkey=接口调用秘钥     */    @RequestMapping("/sso/*")    public Object ssoRequest() {        return SaSsoProcessor.instance.serverDister();    }    /**     * 配置SSO相关参数     */    @Autowired    private void configSso(SaSsoConfig sso) {        // 配置:未登录时返回的View        sso.setNotLoginView(() -> new ModelAndView("sa-login.html"));        // 配置:登录处理函数        sso.setDoLoginHandle((name, pwd) -> {            QueryWrapper queryWrapper = new QueryWrapper<>();            queryWrapper.eq("username", name);            queryWrapper.eq("password", pwd);            AuthLoginUser user = authLoginUserService.getOne(queryWrapper);            if (user != null) {                StpUtil.login(user.getId());                return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());            }            return SaResult.error("登录失败!");        });    }}
Http对外接口--简单几个示例
/** * 前后台分离架构下集成SSO所需的代码 (SSO-Server端) * 

(注:如果不需要前后端分离架构下集成SSO,可删除此包下所有代码)

* * @author kong */@RestControllerpublic class H5Controller { @Autowired private AuthLoginUserService authLoginUserService; /** * 获取 redirectUrl */ @RequestMapping("/sso/getRedirectUrl") private Object getRedirectUrl(String redirect, String mode) { // 未登录情况下,返回 code=401 if (StpUtil.isLogin() == false) { return SaResult.code(401); } // 已登录情况下,构建 redirectUrl if (SaSsoConsts.MODE_SIMPLE.equals(mode)) { // 模式一 SaSsoUtil.checkRedirectUrl(SaFoxUtil.decoderUrl(redirect)); return SaResult.data(redirect); } else { // 模式二或模式三 String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect); return SaResult.data(redirectUrl); } } @RequestMapping("doLogin") public SaResult doLogin(String name, String pwd) { return authLoginUserService.queryUserNameAndPassword(name, pwd); } @RequestMapping(value = "isLogin", method = RequestMethod.GET) public SaResult isLogin() { return SaResult.ok("是否登录:" + StpUtil.isLogin()); } @RequestMapping(value = "tokenInfo", method = RequestMethod.GET) public SaResult tokenInfo() { return SaResult.data(StpUtil.getTokenInfo()); } @RequestMapping(value = "logout", method = RequestMethod.GET) public SaResult logout() { StpUtil.logout(); return SaResult.ok(); }}
Sa-Token的拦截器,权限
/** * @description: 获取当前账号权限码集合 * @author: GuoTong * @createTime: 2022-11-29 20:24 * @since JDK 1.8 OR 11 **/@Component@Slf4jpublic class StpInterfaceImpl implements StpInterface {    @Autowired    private AuthLoginUserService authLoginUserService;    /**     * 返回一个账号所拥有的权限码集合     */    @Override    public List getPermissionList(Object loginId, String loginType) {        AuthLoginUser user = null;        try {            user = authLoginUserService.getById(loginId.toString());        } catch (Exception e) {            log.info("Id无效--->{}", loginId);        }        assert user != null;        String rule = user.getRule();        return Collections.singletonList(rule);    }    /**     * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)     */    @Override    public List getRoleList(Object loginId, String loginType) {        AuthLoginUser user = null;        try {            user = authLoginUserService.getById(loginId.toString());        } catch (Exception e) {            log.info("Id无效--->{}", loginId);        }        assert user != null;        String rule = user.getRule();        return Collections.singletonList(rule);    }}
自定义拦截器,校验Sa-Token登录
/** * @description: 授权认证:HandlerInterceptor需要注册拦截地址哦!!! * @author: GuoTong * @createTime: 2022-11-05 15:40 * @since JDK 1.8 OR 11 **/@Componentpublic class AuthenticationInterceptor implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {        //  获取当前会话是否已经登录,返回true=已登录,false=未登录        String requestURI = httpServletRequest.getRequestURI();        // 判断是否是认证中心对外认证接口        if (requestURI.contains("/sso")) {            return true;        }        // 如果不是映射到方法直接通过        if (!(object instanceof HandlerMethod)) {            return true;        }        HandlerMethod handlerMethod = (HandlerMethod) object;        Method method = handlerMethod.getMethod();        //检查是否有SkipTokenByJWT注释,有则跳过认证        if (method.isAnnotationPresent(SkipTokenByJWT.class)) {            SkipTokenByJWT SkipTokenByJWT = method.getAnnotation(SkipTokenByJWT.class);            if (SkipTokenByJWT.required()) {                return true;            }        }        // 否则需要登陆        if (!StpUtil.isLogin()) {            throw new NotLoginException(ContextCommonMsg.ERROR_MSG_4);        }        //检查有没有需要用户权限的注解        if (method.isAnnotationPresent(NeedTokenByJWT.class)) {            NeedTokenByJWT needTokenByJWT = method.getAnnotation(NeedTokenByJWT.class);            if (needTokenByJWT.required()) {                List permissionList = StpUtil.getPermissionList();                // 查看当前用户是否包含当前接口的权限                if (!permissionList.contains(needTokenByJWT.rule())) {                    throw new NotLoginException(ContextCommonMsg.ERROR_MSG_3);                }            }        }        return true;    }    @Override    public void postHandle(HttpServletRequest httpServletRequest,                           HttpServletResponse httpServletResponse,                           Object o, ModelAndView modelAndView) throws Exception {    }    @Override    public void afterCompletion(HttpServletRequest httpServletRequest,                                HttpServletResponse httpServletResponse,                                Object o, Exception e) throws Exception {    }}
全局异常捕获
/** * @description: 全局异常处理: * @author: GuoTong * @createTime: 2022-11-27 11:17 * @since JDK 1.8 OR 11 **/@RestControllerAdvice@Slf4jpublic class GlobalExceptionHandler {    // 全局异常拦截    @ExceptionHandler    public SaResult handlerException(Exception e) {        e.printStackTrace();        log.error("服务执行异常---->{}", e.getMessage());        return SaResult.error(e.getMessage());    }    @ExceptionHandler(value = NotLoginException.class)    public SaResult handlerException(NotLoginException e) {        log.error("没有登陆---->{}", e.getMessage());        return SaResult.error(e.getMessage());    }    @ExceptionHandler(value = SQLException.class)    public SaResult msgMySQLExecuteError(Exception e) {        e.printStackTrace();        log.error("Mysql执行异常");        String message = e.getMessage();        return SaResult.error(message);    }    @ExceptionHandler(value = HttpMessageNotReadableException.class)    public SaResult msgNotFind(Exception e) {        e.printStackTrace();        log.error("请求错误");        String message = e.getMessage();        return SaResult.error("请求内容未传递" + message);    }}
Springboot配置-跨域-注册拦截器-配置静态资源过滤-RestTemplate负载均衡
/** * @description: SpringBoot-Web配置 * @author: GuoTong * @createTime: 2023-06-05 15:37 * @since JDK 1.8 OR 11 **/@Configurationpublic class SpringBootConfig implements WebMvcConfigurer {    /**     * Description: 增加拦截器     *     * @param registry     * @author: GuoTong     * @date: 2022-11-30 13:56:44     * @return:void     */    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(new AuthenticationInterceptor()).addPathPatterns("/authLoginUser/**");    }    @Bean    @LoadBalanced    public RestTemplate restTemplate() {        return new RestTemplate();    }    /**     * Description:  添加全局跨域CORS处理     */    @Override    public void addCorsMappings(CorsRegistry registry) {        // 设置允许跨域的路径        registry.addMapping("/**")                //设置允许跨域请求的域名                .allowedOriginPatterns("*")                // 是否允许证书                .allowCredentials(true)                // 设置允许的方法                .allowedMethods("GET", "POST", "DELETE", "PUT")                // 设置允许的header属性                .allowedHeaders("*")                // 跨域允许时间                .maxAge(3600);    }    /**     * Description: 静态资源过滤     */    @Override    public void addResourceHandlers(ResourceHandlerRegistry registry) {        //ClassPath:/Static/** 静态资源释放        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");        //释放swagger        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");        //释放webjars        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");    }}

。。。。其余的东西详见项目。。。。。https://gitee.com/gtnotgod/sa-token-sso-system.git

演示

认证中心登录页地址

接口管理认证中心用户

A系统USER-SERVICE依赖
                    org.projectlombok            lombok                                    cn.dev33            sa-token-spring-boot-starter            ${Sa-Token-version}                                    cn.dev33            sa-token-sso            ${Sa-Token-version}                                    cn.dev33            sa-token-dao-redis-jackson            ${Sa-Token-version}                                    cn.dev33            sa-token-alone-redis            ${Sa-Token-version}                                    org.springframework.cloud            spring-cloud-starter-openfeign            ${spring-cloud-starter-version}                                    io.github.openfeign            feign-okhttp            ${feign-okhttp-version}                            com.alibaba.fastjson2            fastjson2            ${fastJson-version}                                    org.springframework.boot            spring-boot-starter-web            ${spring-boot-start-version}                                    com.github.xiaoymin            knife4j-spring-boot-starter            ${knife4j.version}                                    org.springframework.cloud            spring-cloud-starter-bootstrap            ${spring-cloud-starter-version}                            org.springframework.cloud            spring-cloud-starter-netflix-eureka-client            3.1.1                            org.apache.commons            commons-pool2        
核心 SSO-Client 端认证接口
/** * @description: 创建 SSO-Client 端认证接口 * @author: GuoTong * @createTime: 2022-11-27 15:32 * @since JDK 1.8 OR 11 **/@RestControllerpublic class SSOClientController {    /*     * SSO-Client端:处理所有SSO相关请求     *         http://{host}:{port}/sso/login          -- Client端登录地址,接受参数:back=登录后的跳转地址     *         http://{host}:{port}/sso/logout         -- Client端单点注销地址(isSlo=true时打开),接受参数:back=注销后的跳转地址     *         http://{host}:{port}/sso/logoutCall     -- Client端单点注销回调地址(isSlo=true时打开),此接口为框架回调,开发者无需关心     */    @RequestMapping("/sso/*")    public Object ssoRequest() {        return SaSsoProcessor.instance.clientDister();    }    // 首页    @RequestMapping("/")    public String index() {        String str = "

Sa-Token SSO-Client

" + "

当前会话是否登录:" + StpUtil.isLogin() + "

" + "

Login " + "Logout

"; return str; }}
拦截器和认证中心的AuthenticationInterceptor一致核心全局异常处理
@ExceptionHandler(value = NotLoginException.class)    public Resp handlerException(NotLoginException e,                                 HttpServletRequest request,                                 HttpServletResponse response) {        log.error("没有登陆---->{}", e.getMessage());        try {            response.sendRedirect("/sso/login?back=" + request.getRequestURL());        } catch (IOException ex) {            log.error("转到认证中心失败---->{}", ex.getMessage());        }        return Resp.error(e.getMessage());    }
核心配置
spring:  redis:    # Redis数据库索引(默认为0)    database: 1    # Redis服务器地址    host: 127.0.0.1    # Redis服务器连接端口    port: 6379    # Redis服务器连接密码(默认为空)    password: 123456    timeout: 10s    lettuce:      pool:        # 连接池最大连接数        max-active: 20        # 连接池最大阻塞等待时间(使用负值表示没有限制)        max-wait: 10000        # 连接池中的最大空闲连接        max-idle: 3        # 连接池中的最小空闲连接        min-idle: 0  jackson:    date-format: yyyy-MM-dd HH:mm:ss  mvc:    pathmatch:      matching-strategy: ant_path_matcher  #Springboot2.6以上需要手动设置    static-path-pattern: classpath:/static/**  main:    allow-bean-definition-overriding: true      # 重复定义bean的问题logging:  level:    root: info    org.springframework: info# sa-token配置sa-token:  # SSO-相关配置  sso:    # SSO-Server端 统一认证地址    auth-url: http://localhost:15001/sso/auth    # 是否打开单点注销接口    is-slo: true  # 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)  alone-redis:    # Redis数据库索引    database: 1    # Redis服务器地址    host: 127.0.0.1    # Redis服务器连接端口    port: 6379    # Redis服务器连接密码(默认为空)    password: 123456    # 连接超时时间    timeout: 10s    lettuce:      pool:        # 连接池最大连接数        max-active: 200        # 连接池最大阻塞等待时间(使用负值表示没有限制)        max-wait: -1ms        # 连接池中的最大空闲连接        max-idle: 10        # 连接池中的最小空闲连接        min-idle: 0forest:  log-enabled: false   # 关闭 forest 请求日志打印

。。。。其余的东西详见项目。。。。。https://gitee.com/gtnotgod/sa-token-sso-system.git

演示

输入测试接口:http://user.server.com:13601/hello/getOne1/100011

被重定向到认证中心去了

登录访问后

同理准备好B服务THIRD-PARTY-SERVICE选择注销A服务和B服务的其中一个;前提是没有登录多个账号

注销A服务 http://user.server.com:13601/sso/logout注销B服务http://third.server.com:14302/sso/logout

A服务feign调用B服务

具体接口代码:

/**     * 通过主键查询单条数据     *     * @param id 主键     * @return 单条数据     */    @GetMapping("/queryOne/{id}")    public Resp selectOne(@PathVariable("id") String id) {        return Resp.Ok(this.openFeignRPCMySQlService.selectOne(id));    }

fegin接口

/** * @description: OpenFegin调用third-party-service服务 * @FeignClient(name = "third-party-service")  name指定调用的服务名 * @author: GuoTong * @createTime: 2022-12-02 19:39 * @since JDK 1.8 OR 11 **/@FeignClient(name = "third-party-service")public interface OpenFeignRPCMySQlService {    /**     * Description:  feign调用third-party-service服务的接口     *     * @author: GuoTong     * @date: 2022-12-02 20:56:28     * @return:     */    @GetMapping(value = "/hello/getOne2/{id}", produces = "application/json;charset=utf-8")    Resp selectOne(@PathVariable("id") String id);}

输入地址http://user.server.com:13601/hello/queryOne/100011

重定向认证中心

登录A系统

fegin调用成功B系统

A系统已经登录,B系统未登录,访问B系统接口fegin调用A

不再重定向到认证中心,A已经登录,B已完成单点,校验已持有登录状态

注销A系统

http://user.server.com:13601/sso/logout

B系统再调用本系统的接口

已经重定向到认证中心

fegin调用A系统 :http://third.server.com:14302/hello/queryOne/100011

依旧重定向到认证中心

到此单点登录演示实现完毕;

个人项目地址,在gitee:https://gitee.com/gtnotgod/sa-token-sso-system.git

tip: 更多Sa-Token使用教程参考官方地址:https://sa-token.cc/doc.html#/

X 关闭

推荐内容

最近更新

Copyright ©  2015-2022 华南餐饮网版权所有  备案号:粤ICP备18025786号-52   联系邮箱: 954 29 18 82 @qq.com