一、环境搭建
1、创建gulimall-auth-server模块
2、引入登录和注册页
3、修改域名
修改C:\Windows\System32\drivers\etc\hosts的域名
4、复制静态文件到nginx
在虚拟机的/mydata/nginx/html/static/下创建reg,并把静态资源放入reg文件夹下
在虚拟机的/mydata/nginx/html/static/下创建login,并把静态资源放入login文件夹下
5、在网关模块配置认证的路由
二、整合短信服务
1、配置参数
spring:cloud:nacos:discovery:server-addr: 127.0.0.1:8848alicloud:access-key: LTAI5tF8VcyAsK2ghPxTQhGFsecret-key:oss:endpoint: oss-cn-beijing.aliyuncs.combucket: gulimall-adverseqsms:accessKeyId: LTAI4G73Dewcd8U5pC1dppWksecret: MSDVelqAqDtk9RW18ftGMrhC5TKzxstemplateCode: SMS_189520818signName: 谷粒商城application:name: gulimall-third-partyserver:port: 30000
2、SmsApiController
package com.atguigu.gulimall.thirdpart.controller;@RestController@CrossOrigin@RequestMapping("/sms")public class SmsApiController {@Autowiredprivate SendSms sendSms;@Autowiredprivate MyAccess access;@GetMapping("/sendcode")public R sendCode(@RequestParam("phone") String phone,@RequestParam("code")String code){sendSms.send(access,phone,code);return R.ok();}}// 获取配置文件参数package com.atguigu.gulimall.thirdpart.config;@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")@Component@Datapublic class MyAccess {private String accessKeyId;private String secret;private String templateCode;private String signName;}
3、SendSmsImpl
package com.atguigu.gulimall.thirdpart.service.impl;@Servicepublic class SendSmsImpl implements SendSms {@Overridepublic boolean send(MyAccess access,String phoneNum,String code) {//连接阿里云DefaultProfile profile = DefaultProfile.getProfile("cn-qingdao", access.getAccessKeyId(), access.getSecret());IAcsClient client = new DefaultAcsClient(profile);//构建请求CommonRequest request = new CommonRequest();request.setSysMethod(MethodType.POST);request.setSysDomain("dysmsapi.aliyuncs.com");request.setSysVersion("2017-05-25");request.setSysAction("SendSms");//自定义参数(手机号,验证码,签名,模板)request.putQueryParameter("PhoneNumbers", phoneNum);request.putQueryParameter("SignName", access.getSignName());request.putQueryParameter("TemplateCode", access.getTemplateCode());//验证码request.putQueryParameter("TemplateParam",JSONObject.toJSONString(code));try {CommonResponse response = client.getCommonResponse(request);System.out.println(response.getData());return response.getHttpResponse().isSuccess();} catch (ServerException e) {e.printStackTrace();} catch (ClientException e) {e.printStackTrace();}return false;}}
4、创建验证码的常量类
package com.atguigu.common.constant;public class AuthServerConstant {// 验证码前置public static final String SMS_CODE_CACHE_PREFIX="sms:code:";}
5、添加常量
添加验证码60秒内重复获取验证码的错误提示常量
public enum BizCodeEnume {SMS_CODE_EXCEPTION(10002,"发送验证码太频繁,请稍后再试"),private int code;private String msg;BizCodeEnume(int code, String msg){this.code = code;this.msg = msg;}public int getCode() {return code;}public String getMsg() {return msg;}}
6、远程调用验证码接口
package com.atguigu.gulimall.auth.feign.ThirdPartFeignService;@FeignClient("gulimall-thrid-party")public interface ThirdPartFeignService {@GetMapping("/sms/sendcode")public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code);}
7、接口防刷
由于发送验证码的接口暴露,为了防止恶意攻击,我们不能随意让接口被调用。<br /> 1)在redis中以phone-code将电话号码和验证码进行存储并将当前时间与code一起存储<br /> 2)如果调用时以当前phone取出的v不为空且当前时间在存储时间的60s以内,说明60s内该号码已经调用过,返回错误信息<br /> 3)60s以后再次调用,需要删除之前存储的phone-code<br /> 4)code存在一个过期时间,我们设置为10min,10min内验证该验证码有效
8、LoginController
/*** 发送手机验证码*/@ResponseBody@GetMapping("/sms/sendcode")public R sendCode(@RequestParam("phone") String phone) {//TODO 接口防刷if(!StringUtils.isEmpty(phone) & phone != null) {String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);if (!StringUtils.isEmpty(redisCode)) {long time = Long.parseLong(redisCode.split("_")[1]);if (System.currentTimeMillis() - time < 60000) {// 60秒内不能再发return R.error(BizCodeEnume.SMS_CODE_EXCEPTION.getCode(), BizCodeEnume.SMS_CODE_EXCEPTION.getMsg());}}// 2.验证码的再次校验。redis:key->phone value->code sms:code:18312345678->45678// 防止同一个手机号在60秒内再次发送验证码,加时间戳后缀// String code1 = UUID.randomUUID().toString().substring(0, 5)+"_"+System.currentTimeMillis();String code = "123456" + "_" + System.currentTimeMillis();// redis缓存验证码stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone, code, 10, TimeUnit.MINUTES);// 远程调用第三方服务发送验证码// thirdPartFeignService.sendCode(phone,code1);return R.ok();}else {return R.error(BizCodeEnume.PHONE_NULL_EXCEPTION.getCode(), BizCodeEnume.PHONE_NULL_EXCEPTION.getMsg());}}
9、测试


三、用户注册
1、添加视图控制器
2、导入参数校验依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
1)若JSR303校验未通过,则通过BindingResult封装错误信息,并重定向至注册页面
2)若通过JSR303校验,则需要从redis中取值判断验证码是否正确,正确的话通过会员服务注册
3)会员服务调用成功则重定向至登录页,否则封装远程服务返回的错误信息返回至注册页面
3、UserRegistVo
@Datapublic class UserRegistVo {@NotEmpty(message = "用户名不能为空")@Length(min =6, max = 18, message = "用户名必须是6~18位字符")private String userName;@NotEmpty(message = "密码不能为空")@Length(min =6, max = 18, message = "密码必须是6~18位字符")private String password;@NotEmpty(message = "手机号不能为空")@Pattern(regexp = "^[1]([3-9])[0-9]{9}$", message = "手机号码格式不正确")private String phone;@NotEmpty(message = "验证码不能为空")private String code;}
4、LoginController
/*** 用户注册* 重定向携带数据,利用session原理。将数据放在session中。只要跳到下一个页面,取出数据以后,* session里面的数据就会删掉** @param vo* @param result* @param redirectAttributes 模拟重定向携带数据* @return 注册成功回到首页,或回到登录页*/@PostMapping("/regist")public String regist(@Valid UserRegistVo vo, BindingResult result,RedirectAttributes redirectAttributes) {if (result.hasErrors()) {/*** 方法一* Map<String, String> errors = result.getFieldErrors().stream().map(fieldError ->{* String field = fieldError.getField();* String defaultMessage = fieldError.getDefaultMessage();* errors.put(field,defaultMessage);* return errors;* }).collect(Collector.asList());*/// 方法二:// 1.1 如果校验不通过,则封装校验结果Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(fieldError -> {return fieldError.getField(); // Map的键}, fieldError -> {return fieldError.getDefaultMessage(); // Map的值}));// 1.2 将错误信息封装到session中,并在重定向的时候携带过去redirectAttributes.addFlashAttribute("errors", errors);/*** 使用 return "forward:/reg.html"; 会出现* 问题:Request method 'POST' not supported的问题* 原因:用户注册-> /regist[post] ------>转发/reg.html (路径映射默认都是get方式访问的)*/// return "reg"; //转发会出现重复提交的问题,不要以转发的方式// 1.3 如果校验出错,重定向到注册页(转发会重复提交表单)。但面临着数据不能携带的问题,就用RedirectAttributes可解决return "redirect:http://auth.gulimall.com/reg.html";}// 2 校验验证码String code = vo.getCode();String s = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vo.getPhone());if (!StringUtils.isEmpty(s)) {if (code.equals(s.split("_")[0])) {// 验证码通过// 删除验证码,令牌机制stringRedisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vo.getPhone());// 调用远程服务,注册R r = memberFeignService.regist(vo);if (r.getCode() == 0) {//注册成功回到登录页return "redirect:http://auth.gulimall.com/login.html";} else {Map<String, String> errors = new HashMap<>();errors.put("msg", r.getData2("msg", new TypeReference<String>() { }));redirectAttributes.addFlashAttribute("errors", errors);return "redirect:http://auth.gulimall.com/reg.html";}} else {Map<String, String> errors = new HashMap<>();errors.put("code", "验证码错误");redirectAttributes.addFlashAttribute("errors", errors);return "redirect:http://auth.gulimall.com/reg.html";}} else {Map<String, String> errors = new HashMap<>();errors.put("code", "验证码错误");redirectAttributes.addFlashAttribute("errors", errors);// 校验出错转发到注册页return "redirect:http://auth.gulimall.com/reg.html";}}
5、远程调用会员服务注册用户接口
package com.atguigu.gulimall.auth.feign;@FeignClient("gulimall-member")public interface MemberFeignService {/*** 远程调用member的用户注册接口* @param vo* @return*/@PostMapping("/member/member/regist")R regist(@RequestBody UserRegistVo vo);}
6、gulimall-member服务的MemberController
/*** 用户注册* @param vo* @return*/@PostMapping("/regist")public R regist(@RequestBody MemberRegistVo vo) {try {memberService.regist(vo);// 通过异常机制判断当前注册会员名和电话号码是否已经注册,如果已经注册,则抛出对应的自定义异常,并在返回时封装对应的错误信息} catch (PhoneExistException e) {return R.error(BizCodeEnume.PHONE_EXIST_EXCEPTION.getCode(), BizCodeEnume.PHONE_EXIST_EXCEPTION.getMsg());} catch (UserNameExistException e) {return R.error(BizCodeEnume.USER_EXIST_EXCEPTION.getCode(), BizCodeEnume.USER_EXIST_EXCEPTION.getMsg());}return R.ok();}
7、自定义异常
package com.atguigu.gulimall.member.exception;public class PhoneExistException extends RuntimeException {public PhoneExistException(){super("该手机号码已注册");}}
package com.atguigu.gulimall.member.exception;public class UserNameExistException extends RuntimeException {public UserNameExistException(){super("用户名已存在");}}
8、MD5&MD5盐值与BCrypt加密
9、MemberServiceImpl
@Overridepublic void regist(MemberRegistVo vo) {MemberDao dao = this.baseMapper;MemberEntity entity = new MemberEntity();// 设置默认用户等级MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();entity.setLevelId(levelEntity.getId());// 检查数据是否唯一,为了让controller感知异常,异常机制checkPhoneUnique(vo.getPhone());checkUserNameUnique(vo.getUserName());entity.setMobile(vo.getPhone());entity.setUsername(vo.getUserName());entity.setNickname(vo.getUserName());// 密码加密存储BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();String encode = passwordEncoder.encode(vo.getPassword());entity.setPassword(encode);dao.insert(entity);// 其它默认信息}@Overridepublic void checkPhoneUnique(String phone) throws PhoneExistException {MemberDao dao = this.baseMapper;Integer mobile = dao.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));if (mobile > 0) {throw new PhoneExistException();}}@Overridepublic void checkUserNameUnique(String userName) throws UserNameExistException {MemberDao dao = this.baseMapper;Integer cout = dao.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));if (cout > 0) {throw new UserNameExistException();}}
四、用户名密码登录
1、UserLoginVo
package com.atguigu.gulimall.auth.vo;@Datapublic class UserLoginVo {private String loginacct;private String password;}
2、LoginController
@PostMapping("/login")// 前端传来k,v参数不需要加@RequestBodypublic String login(UserLoginVo vo,RedirectAttributes redirectAttributes,HttpSession session) {//远程登录R r = memberFeignService.login(vo);if (r.getCode() == 0) {MemberRespVo data = r.getData(new TypeReference<MemberRespVo>() { });session.setAttribute(AuthServerConstant.LOGIN_USER, data);return "redirect:http://gulimall.com";} else {Map<String,String> errors = new HashMap<>();errors.put("msg",r.getData2("msg",new TypeReference<String>(){}));redirectAttributes.addFlashAttribute("errors",errors);return "redirect:http://auth.gulimall.com/login.html";}}
3、远程调用member的用户登录接口
package com.atguigu.gulimall.auth.feign;@FeignClient("gulimall-member")public interface MemberFeignService {/*** 远程调用member的用户登录接口* @param vo* @return*/@PostMapping("/member/member/login")R login(@RequestBody UserLoginVo vo);}
4、会员服务的MemberController
@PostMapping("/login")public R login(@RequestBody MemberLoginVo vo) {MemberEntity memberEntity = memberService.login(vo);if (memberEntity != null) {return R.ok().setData(memberEntity);} else {return R.error(BizCodeEnume.LOGINACCT_PASSWORD_INVAILD_EXCEPTION.getCode(), BizCodeEnume.LOGINACCT_PASSWORD_INVAILD_EXCEPTION.getMsg());}}
package com.atguigu.common.exception;public enum BizCodeEnume {LOGINACCT_PASSWORD_INVAILD_EXCEPTION(15002,"账号或密码错误");private int code;private String msg;BizCodeEnume(int code, String msg){this.code = code;this.msg = msg;}public int getCode() {return code;}public String getMsg() {return msg;}}
5、MemberServiceImpl
@Overridepublic MemberEntity login(MemberLoginVo vo) {String loginacct = vo.getLoginacct();String password = vo.getPassword();//1.数据库查询MemberDao dao = this.baseMapper;QueryWrapper<MemberEntity> wrapper = new QueryWrapper<>();wrapper.eq("username", loginacct).or().eq("mobile", loginacct);MemberEntity entity = dao.selectOne(wrapper);if (entity == null) {return null;} else {String passwordDb = entity.getPassword();BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();//密码匹配boolean matches = passwordEncoder.matches(password, passwordDb);if (matches) {return entity;} else {return null;}}}
五、社交登录
六、SpringSession
1、session原理
2、分布式下session共享问题
3、Session共享问题解决
3.1 session复制
3.2 客户端存储
3.3 hash一致性
3.4 统一存储
3.5 不同服务、子域session共享
jsessionid这个cookie默认是当前系统域名的。当我们分拆服务,不同域名部署的时候,我们可以使用如下解决方案:
4、整合SpringSession
4.1 导入依赖
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency>
4.2 设置session的保存类型
spring.session.store-type=redis session的保存类型server.servlet.session.timeout=30m session的过期时间
4.3 配置redis连接信息
spring.redis.host=192.168.195.128spring.redis.port=6379
4.4 主配置类添加注解
package com.atguigu.gulimall.auth;@EnableDiscoveryClient@SpringBootApplication// 整合redis存储session// 创建了一个springSessionRepositoryFilter// 负责将原生HttpSession替换为Spring Session的实现@EnableRedisHttpSession@EnableFeignClientspublic class GulimallAuthServerApplication {public static void main(String[] args) {SpringApplication.run(GulimallAuthServerApplication.class, args);}}
4.5 自定义配置
- 由于默认使用jdk进行序列化,通过导入RedisSerializer修改为json序列化
- 并且通过修改CookieSerializer扩大session的作用域至**.gulimall.com ```java package com.atguigu.gulimall.auth.config;
@Configuration public class GulimallSessionConfig {
// Cookie序列化器@Beanpublic CookieSerializer cookieSerializer(){DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();// 设置session作用域cookieSerializer.setDomainName("gulimall.com");// 设置Cookie的名称cookieSerializer.setCookieName("GULISESSION");return cookieSerializer;}@Beanpublic RedisSerializer<Object> springSessionDefaultRedisSerializer(){// 序列化机制return new GenericJackson2JsonRedisSerializer();}
}
<a name="yjVep"></a>## 5、SpringSession核心原理<br /><a name="DNPZS"></a>## 6、分布式登录总结首先判断session中是否有loginUser对象<br />1)没有loginUser对象,渲染login.html页面<br /> 用户输入账号密码后发送给url:auth.gulimall.com/login。根据表单传过来的VO对象,远程调用memberFeignService验证密码<br /> 如果验证失败,取出远程调用返回的错误信息,放到新的请求域,重定向到登录url<br /> 如果验证成功,远程服务就返回了对应的MemberRespVo对象,然后放到分布式redis-session中,key为"loginUser",重定向到首页gulimall.com,同时也会带着的GULISESSIONID<br /> 重定向到非auth项目后,先经过拦截器看session里有没有loginUser对象<br /> 有,放到静态threadLocal中,这样就可以操作本地内存,无需远程调用session<br /> 没有,重定向到登录页<br />2)有loginUser对象,代表登录过了,重定向到首页,session数据还依靠sessionID持有着额外说明:<br />问题1:我们有sessionId不就可以了吗?为什么还要在session中放到User对象?<br />为了其他服务可以根据这个user查数据库,只有session的话不能再次找到登录session的用户问题2:threadlocal的作用?<br />它是为了放到当前session的线程里,threadlocal就是这个作用,随着线程创建和消亡。把threadlocal定义为static的,这样当前会话的线程中任何代码地方都可以获取到。如果只是在session中的话,一是每次还得去redis查询,二是去调用service还得传入session参数,多麻烦啊问题3:cookie怎么回事?不是在config中定义了cookie的key和序列化器?<br />序列化器没什么好讲的,就是为了易读和来回转换。而cookie的key其实是无所谓的,只要两个项目里的key相同,然后访问同一个域名都带着该cookie即可。<a name="O7tgw"></a># 七、单点登录<br /><br /><a name="Z2yFB"></a>## 1、gulimall-test-sso-server<a name="Pg6wk"></a>### 1.1 导入依赖```java<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.11.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.atguigu</groupId><artifactId>gulimall-test-sso-server</artifactId><version>0.0.1-SNAPSHOT</version><name>gulimall-test-sso-server</name><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
1.1 LoginController
package com.atguigu.gulimall.ssoserver.controller;@Controllerpublic class LoginController {@AutowiredStringRedisTemplate redisTemplate;@GetMapping("/userInfo")@ResponseBodypublic String userInfo(@RequestParam("token") String token){String s = redisTemplate.opsForValue().get(token);return s;}@GetMapping("/login.html")public String loginPage(@RequestParam("redirect_url") String url,@CookieValue(value = "sso_token",required = false) String sso_token,Model model) {// 判断是否登录过?依据是否拥有cookie sso_token,如果有直接返回之前的页面if(!StringUtils.isEmpty(sso_token)){return "redirect:" + url + "?token=" + sso_token;}model.addAttribute("url", url);return "login";}@PostMapping("/doLogin")public String doLogin(@RequestParam("username") String username,@RequestParam("password") String password,@RequestParam("url") String url,HttpServletResponse response) {// 登录成功跳转,跳回之前的页面if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {// 保存登录成功的用户,例如redisString uuid = UUID.randomUUID().toString().replace("-", "");redisTemplate.opsForValue().set(uuid, username, 30, TimeUnit.MINUTES);Cookie sso_token = new Cookie("sso_token",uuid);response.addCookie(sso_token);return "redirect:" + url + "?token=" + uuid;}// 登录失败,展示登录页return "login";}}
1.2 login.html
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="/doLogin" method="post">用户名: <input name="username" type="text"><br/>密码: <input name="password" type="password"> <br/><input type="hidden" name="url" th:value="${url}"><input type="submit" value="登录"></form></body></html>
2、gulimall-test-sso-client
package com.atguigu.gulimall.ssoclient;@SpringBootApplicationpublic class GulimallTestSsoClient2Application {public static void main(String[] args) {SpringApplication.run(GulimallTestSsoClient2Application.class, args);}}
2.1 application.properties
server.port=8081sso.server.url=http://ssoserver.com:8080/login.html
2.2 HelloController
package com.atguigu.gulimall.ssoclient.controller;@Controllerpublic class HelloController {@Value("${sso.server.url}")String ssoServerUrl;// 无需登录即可访问@ResponseBody@GetMapping("/hello")public String hello(){return "hello";}/*** 可以感知登录服务器登录成功返回* ssoserver登录成功返回就会带上token* @return*/@GetMapping("/employees")public String emploees(Model model, HttpSession session,@RequestParam(value = "token",required = false) String token){if(!StringUtils.isEmpty(token)){// 去sso服务器获取当前token真正对应的用户信息RestTemplate restTemplate = new RestTemplate();ResponseEntity<String> forEntity = restTemplate.getForEntity("http://ssoserver.com:8080/userInfo?token=" + token, String.class);String body = forEntity.getBody();session.setAttribute("loginUser",body);}Object loginUser = session.getAttribute("loginUser");if(loginUser == null){// 没有登录,跳转到登录服务器进行登录return "redirect:"+ssoServerUrl+"?redirect_url=http://client1.com:8081/employees";}else {List<String> emps = new ArrayList<String>();emps.add("张三");emps.add("李四");emps.add("王五");model.addAttribute("emps", emps);return "list";}}}
2.3 list.html
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title>Title</title></head><body><h1>[[${session.loginUser}]]</h1><ul><li th:each="emp:${emps}">姓名:[[${emp}]]</li></ul></body></html>
3、gulimall-test-sso-client2
package com.atguigu.gulimall.ssoclient;@SpringBootApplicationpublic class GulimallTestSsoClient2Application {public static void main(String[] args) {SpringApplication.run(GulimallTestSsoClient2Application.class, args);}}
3.1 application.properties
server.port=8082sso.server.url=http://ssoserver.com:8080/login.html
3.2 HelloController
package com.atguigu.gulimall.ssoclient.controller;@Controllerpublic class HelloController {@Value("${sso.server.url}")String ssoServerUrl;// 无需登录即可访问@ResponseBody@GetMapping("/hello")public String hello(){return "hello";}/*** 可以感知登录服务器登录成功返回* ssoserver登录成功返回就会带上token* @return*/@GetMapping("/boss")public String emploees(@RequestParam(value = "token",required = false) String token,Model model, HttpSession session){if(!StringUtils.isEmpty(token)){// 去sso服务器获取当前token真正对应的用户信息RestTemplate restTemplate = new RestTemplate();ResponseEntity<String> forEntity = restTemplate.getForEntity("http://ssoserver.com:8080/userInfo?token=" + token, String.class);String body = forEntity.getBody();session.setAttribute("loginUser",body);}Object loginUser = session.getAttribute("loginUser");if(loginUser == null){// 没有登录,跳转到登录服务器进行登录return "redirect:" + ssoServerUrl + "?redirect_url=http://client2.com:8082/boss";}else {List<String> emps = new ArrayList<String>();emps.add("Jack");emps.add("Tom");emps.add("Ada");model.addAttribute("emps", emps);return "list";}}}
3.3 list.html
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title>Title</title></head><body><h1>欢迎:[[${session.loginUser}]]</h1><ul><li th:each="emp:${emps}">姓名:[[${emp}]]</li></ul></body></html>
