帶你逐步深入瞭解SSM框架——淘淘商城專案之單點登入系統實現
1. 課程計劃
1、 實現單點登入系統
2、 實現使用者的登入功能
3、 實現使用者的註冊功能
2. 單點登入系統分析
2.1. 什麼是SSO
SSO英文全稱Single Sign On,單點登入。SSO是在多個應用系統中,使用者只需要登入一次就可以訪問所有相互信任的應用系統。它包括可以將這次主要的登入對映到其他應用中用於同一個使用者的登入的機制。它是目前比較流行的企業業務整合的解決方案之一。
2.2. 原來的登入邏輯實現
2.2.1. 問題
單臺tomcat,以上實現是沒有任何問題的,但是我們現在是叢集的tomcat,就會存在session共享問題。
只要解決session共享問題,登入問題即可解決。
每個系統都有自己的session,不能統一。
2.2.2. 解決session共享問方案
1、 tomcat的session複製
優點:不需要額外開發,只需要搭建tomcat叢集即可。
缺點:tomcat 是全域性session複製,叢集內每個tomcat的session完全同步(也就是任何時候都完全一樣的) 在大規模應用的時候,使用者過多,叢集內tomcat數量過多,session的全域性複製會導致叢集效能下降, 因此,tomcat的數量不能太多,5個以下為好。
2、 實現單點登入系統,提供服務介面。把session資料存放在redis。
Redis可以設定key的生存時間、訪問速度快效率高。
優點:redis存取速度快,不會出現多個節點session複製的問題。效率高。
缺點:需要程式設計師開發。
2.3. 單點登入系統的流程
3. SSO開發
3.1. 系統架構
3.2. 開發SSO服務
3.2.1. 建立sso服務工程
3.2.2. Pom.xml
Pom.xml檔案參考taotao-rest工程的pom檔案。
3.2.3. 服務開發
a) 登入介面
b) 註冊介面
c) 查詢介面
d) 退出登入介面
開發的流程:
1、 確定流程、確定介面內容
2、 提供介面文件
a) 介面地址
b) 入參說明
c) 介面訪問方式,get、post
d) 結果輸出,說明格式
e) 介面呼叫的示例
3、 約定聯合測試的時間點
4、 釋出上線
參考:SSO介面文件.docx
3.3. 開發註冊介面
1、 需要對使用者提交的資料做校驗
2、 對密碼做md5加密
3、 對報錯異常要做處理
3.3.1. 資料校驗介面
1. Mapper
對tb_user表進行單表查詢。使用逆向工程生成的mapper即可。
2. Service
@Service public class UserRegisterServiceImpl implements UserRegisterService { @Autowired private TbUserMapper userMapper; @Override public TaotaoResult checkInfo(String value, String type) throws Exception { boolean result = false; //type為型別,可選引數1、2、3分別代表username、phone、email if ("1".equals(type)) { result = checkUserName(value); } else if ("2".equals(type)) { result = checkPhone(value); } else if ("3".equals(type)) { result = checkEmail(value); } //返回結果 if (result) { return TaotaoResult.ok(result); } return TaotaoResult.build(201, "此數值已經存在"); } private boolean checkUserName(String userName) throws Exception { //建立查詢條件 TbUserExample example = new TbUserExample(); Criteria criteria = example.createCriteria(); criteria.andUsernameEqualTo(userName); List<TbUser> list = userMapper.selectByExample(example); //判斷結果中是否存在 if (list == null || list.isEmpty()) { return true; } return false; } private boolean checkPhone(String phone) throws Exception { //建立查詢條件 TbUserExample example = new TbUserExample(); Criteria criteria = example.createCriteria(); criteria.andPhoneEqualTo(phone); List<TbUser> list = userMapper.selectByExample(example); //判斷結果中是否存在 if (list == null || list.isEmpty()) { return true; } return false; } private boolean checkEmail(String email) throws Exception { //建立查詢條件 TbUserExample example = new TbUserExample(); Criteria criteria = example.createCriteria(); criteria.andEmailEqualTo(email); List<TbUser> list = userMapper.selectByExample(example); //判斷結果中是否存在 if (list == null || list.isEmpty()) { return true; } return false; } } |
3. Controller
@Controller @RequestMapping("/user") public class UserRegisterController { @Autowired private UserRegisterService userRegisterService; @RequestMapping("/check/{param}/{type}") @ResponseBody public Object checkInfo(@PathVariable String param, @PathVariable String type, String callback) { TaotaoResult result = null; try { result = userRegisterService.checkInfo(param, type); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); result = TaotaoResult.build(500, ExceptionUtil.getStackTrace(e)); } //支援jsonp if (!StringUtils.isBlank(callback)) { MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result); mappingJacksonValue.setJsonpFunction(callback); return mappingJacksonValue; } return result; } } |
3.3.2. 使用者註冊介面
1. service
@Override public TaotaoResult register(TbUser user) throws Exception { //有效性驗證 if (StringUtils.isBlank(user.getUsername())) { return TaotaoResult.build(400, "使用者名稱不能為空"); } if (StringUtils.isBlank(user.getPassword())) { return TaotaoResult.build(400, "密碼不能為空"); } if (StringUtils.isBlank(user.getPhone())) { return TaotaoResult.build(400, "手機不能為空"); } //轉換md5 user.setPassword(DigestUtils.md5DigestAsHex(user.getPassword().getBytes())); //完善user資訊 user.setCreated(new Date()); user.setUpdated(new Date()); //新增到資料庫 userMapper.insert(user); return TaotaoResult.ok(); } |
2. Controller
@RequestMapping(value="/register", method=RequestMethod.POST) public TaotaoResult register(TbUser user) { TaotaoResult taotaoResult = null; try { taotaoResult = userRegisterService.register(user); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return TaotaoResult.build(500, ExceptionUtil.getStackTrace(e)); } returntaotaoResult; } |
3.4. 開發登入介面
3.4.1. Service
@Service public class UserLoginServiceImpl implements UserLoginService { @Autowired private TbUserMapper userMapper; @Autowired private JedisCluster jedisCluster; @Value("${USER_TOKEN_KEY}") private String USER_TOKEN_KEY; @Value("${SESSION_EXPIRE_TIME}") private Integer SESSION_EXPIRE_TIME; @Override public TaotaoResult login(String username, String password) throws Exception { //根據使用者名稱查詢使用者資訊 TbUserExample example = new TbUserExample(); Criteria criteria = example.createCriteria(); criteria.andUsernameEqualTo(username); List<TbUser> list = userMapper.selectByExample(example); if (null == list || list.isEmpty()) { return TaotaoResult.build(400, "使用者不存在"); } //核對密碼 TbUser user = list.get(0); if (!DigestUtils.md5DigestAsHex(password.getBytes()).equals(user.getPassword())) { return TaotaoResult.build(400, "密碼錯誤"); } //登入成功,把使用者資訊寫入redis //生成一個使用者token String token = UUID.randomUUID().toString(); jedisCluster.set(USER_TOKEN_KEY + ":" + token, JsonUtils.objectToJson(user)); //設定session過期時間 jedisCluster.expire(USER_TOKEN_KEY + ":" + token, SESSION_EXPIRE_TIME); return TaotaoResult.ok(token); } } |
3.4.2. Controller
@Controller @RequestMapping("/user") public class UserLoginController { @Autowired private UserLoginService userLoginService; @RequestMapping(value="/login", method=RequestMethod.POST) @ResponseBody public TaotaoResult login(String username, String password) { TaotaoResult result = null; try { result = userLoginService.login(username, password); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return TaotaoResult.build(500, ExceptionUtil.getStackTrace(e)); } returnresult; } } |
3.5. 根據token查詢使用者
3.5.1. Service
@Service public class UserTokenServiceImpl implements UserTokenService { @Autowired private JedisCluster jedisCluster; @Value("${USER_TOKEN_KEY}") private String USER_TOKEN_KEY; @Value("${SESSION_EXPIRE_TIME}") private Integer SESSION_EXPIRE_TIME; /** * 根據token取使用者資訊 * <p>Title: getUserByToken</p> * <p>Description: </p> * @param token * @return * @throws Exception * @see com.taotao.sso.service.UserTokenService#getUserByToken(java.lang.String) */ @Override public TaotaoResult getUserByToken(String token) throws Exception { //從redis中取使用者資訊 String userJson = jedisCluster.get(USER_TOKEN_KEY + ":" + token); if (StringUtils.isBlank(userJson)) { return TaotaoResult.build(400, "該使用者已過期"); } //把json轉換成user物件 TbUser user = JsonUtils.jsonToPojo(userJson, TbUser.class); //更新使用者有效期 jedisCluster.expire(USER_TOKEN_KEY + ":" + token, SESSION_EXPIRE_TIME); return TaotaoResult.ok(user); } } |
3.5.2. Controller
@Controller @RequestMapping("/user") public class UserTokenController { @Autowired private UserTokenService userTokenService; @RequestMapping("/token/{token}") @ResponseBody public Object getUserByToken(@PathVariable String token, String callback) { TaotaoResult result = null; try { result = userTokenService.getUserByToken(token); } catch (Exception e) { e.printStackTrace(); result = TaotaoResult.build(500, ExceptionUtil.getStackTrace(e)); } //判斷是否為jsonp呼叫 if (!StringUtils.isBlank(callback)) { MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result); mappingJacksonValue.setJsonpFunction(callback); return mappingJacksonValue; } return result; } } |
4. 使用者註冊
4.1. 需求分析
使用者註冊時需要呼叫taotao-sso的服務檢查使用者的有效性以及使用者、手機是否存在。檢查通過後提交表單至taotao-portal的controller,由taotao-portal呼叫taotao-sso的服務註冊使用者。
4.2. 有效性檢查
4.3. 使用者註冊實現
4.3.1. Service
@Service public class UserServiceImpl implements UserService { @Value("${SSO_BASE_URL}") private String SSO_BASE_URL; @Value("${REGISTER_USER_URL}") private String REGISTER_USER_URL; @Override public TaotaoResult register(TbUser user) { //請求引數 Map<String, String> param = new HashMap<>(); param.put("username", user.getUsername()); param.put("password", user.getPassword()); param.put("phone", user.getPhone()); param.put("email", user.getEmail()); //提交使用者資訊 String stringResult = HttpClientUtil.doPost(SSO_BASE_URL + REGISTER_USER_URL, param); TaotaoResult result = TaotaoResult.format(stringResult); returnresult; } } |
4.3.2. Controller
@RequestMapping("/doregister") @ResponseBody public TaotaoResult doRegister(TbUser user) throws Exception { TaotaoResult result = userService.register(user); return result; } |
5. 使用者登入
5.1. 需求
前臺頁面提交使用者名稱、密碼至taotao-portal,由taotao-poratl呼叫sso系統的服務做登入處理。登入成功後返回使用者token資料。taotao-portal把token寫入cookie,返回登入成功。頁面接收到成功資訊後,跳轉至商品列表頁面。商品列表頁面從cookie中取出token資訊,根據token查詢使用者並顯示到首頁。
5.2. Service
public TaotaoResult login(String username, String password, HttpServletRequest request, HttpServletResponse response) { //請求引數 Map<String, String> param = new HashMap<>(); param.put("username", username); param.put("password", password); //登入處理 String stringResult = HttpClientUtil.doPost(REGISTER_USER_URL + USER_LOGIN_URL, param); TaotaoResult result = TaotaoResult.format(stringResult); //登入出錯 if (result.getStatus() != 200) { returnresult; } //登入成功後把取token資訊,並寫入cookie String token = (String) result.getData(); //寫入cookie CookieUtils.setCookie(request, response, "TT_TOKEN", token); //返回成功 returnresult; } |
5.3. Controller
/** * 登入處理 * <p>Title: doLogin</p> * <p>Description: </p> * @param username * @param password * @param request * @param response * @return */ @RequestMapping("/dologin") @ResponseBody public TaotaoResult doLogin(String username, String password, HttpServletRequest request, HttpServletResponse response) { TaotaoResult result = userService.login(username, password, request, response); returnresult; } |
5.4. 顯示當前的使用者名稱
Taotao.js
var TT = TAOTAO = {
|