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() + "
" + ""; 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
被重定向到认证中心去了
登录访问后
A服务feign调用B服务注销A服务 http://user.server.com:13601/sso/logout注销B服务http://third.server.com:14302/sso/logout
具体接口代码:
/** * 通过主键查询单条数据 * * @param id 主键 * @return 单条数据 */ @GetMapping("/queryOne/{id}") public Resp
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
输入地址http://user.server.com:13601/hello/queryOne/100011
重定向认证中心
登录A系统
A系统已经登录,B系统未登录,访问B系统接口fegin调用Afegin调用成功B系统
注销A系统不再重定向到认证中心,A已经登录,B已完成单点,校验已持有登录状态
B系统再调用本系统的接口http://user.server.com:13601/sso/logout
已经重定向到认证中心
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