單點登陸(Single Sign On),簡稱爲 SSO,是目前比較流行的企業業務整合的解決方案之一。SSO 的定義是在多個應用系統中,用戶只須要登陸一次就能夠訪問全部相互信任的應用系統。css
實現單點登陸,就是解決如何產生和存儲那個信任,再就是其餘系統如何驗證這個信任的有效性。所以,也就須要解決如下兩點:html
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 添加服務消費者的標誌 --> <dubbo:application name="ego-sso-web-consumer"/> <!-- 指定註冊中心,有兩個地址192.168.1.17一、172.18.25.171 --> <dubbo:registry address="172.18.25.171:2181,172.18.25.171:2182,172.18.25.171:2183" protocol="zookeeper" /> <!--<dubbo:registry address="192.168.1.171:2181,192.168.1.171:2182,192.168.1.171:2183" protocol="zookeeper" />--> <!-- spring容器中存在一個遠程服務的代理對象 --> <dubbo:reference interface="com.soldier.ego.rpc.service.UserService" id="userServiceProxy"></dubbo:reference> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 加載cache.properties--> <!--<context:property-placeholder location="classpath:cache.properties"></context:property-placeholder>--> <!-- 實例化JedisCluster,鏈接redis集羣,有兩個地址192.168.1.17四、172.18.25.174--> <bean id="jedisCluster" class="redis.clients.jedis.JedisCluster"> <constructor-arg name="nodes"> <set> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="172.18.25.174"></constructor-arg> <constructor-arg name="port" value="6380"></constructor-arg> </bean> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="172.18.25.174"></constructor-arg> <constructor-arg name="port" value="6381"></constructor-arg> </bean> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="172.18.25.174"></constructor-arg> <constructor-arg name="port" value="6382"></constructor-arg> </bean> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="172.18.25.174"></constructor-arg> <constructor-arg name="port" value="6383"></constructor-arg> </bean> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="172.18.25.174"></constructor-arg> <constructor-arg name="port" value="6384"></constructor-arg> </bean> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="172.18.25.174"></constructor-arg> <constructor-arg name="port" value="6385"></constructor-arg> </bean> </set> </constructor-arg> </bean> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.soldier.ego.sso.service.impl" /> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 掃描controller --> <context:component-scan base-package="com.soldier.ego.sso.controller" /> <!-- mvc:annotation-driven --> <mvc:annotation-driven /> <!-- 靜態資源映射 --> <!-- location:表示資源在項目的真正位置 --> <!-- mapping:訪問路徑 --> <!-- /css/** --> <!-- http://localhost:8080/css/a/b/c/hello.css --> <!-- / = http://localhost:8080/ --> <mvc:resources location="/css/" mapping="/css/**"></mvc:resources> <mvc:resources location="/js/" mapping="/js/**"></mvc:resources> <mvc:resources location="/images/" mapping="/images/**"></mvc:resources> <!-- 視圖解析器 --> <bean id="viewResovler" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 表示使用的視圖技術是jsp --> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property> <!-- 前綴 --> <property name="prefix" value="/WEB-INF/jsp/"></property> <!-- 後綴 --> <property name="suffix" value=".jsp"></property> </bean> </beans>
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>ego-sso-web</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <!--<servlet-mapping>--> <!--<servlet-name>default</servlet-name>--> <!--<url-pattern>/favicon.ico</url-pattern>--> <!--</servlet-mapping>--> <!-- 以監聽器的方式啓動spring容器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 指定spring的配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext-*.xml</param-value> </context-param> <!-- POST請求的亂碼過濾器 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <!-- 指定編碼方式 --> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <!-- 映射filter --> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- springmvc的servlet --> <servlet> <servlet-name>ego-sso-web</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 指定springmvc的配置文件 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/springmvc.xml</param-value> </init-param> <!-- 讓springmvc隨系統啓動而啓動 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ego-sso-web</servlet-name> <!-- 不須要僞靜態化--> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
請求方法 | GET |
UR | http://sso.egou.com/user/check/{param}/{type} |
參數說 | 格式如:zhangsan/1,其中 zhangsan 是校驗的數據,type 爲類型,可 選參數 一、二、3 分別表明 username、phone、email 可選參數 callback:若是有此參數表示此方法爲 jsonp 請求,須要支 持 jsonp。 |
示例 | http://sso.egou.com/user/check/zhangsan/1 |
返回 | { status: 200 //200 成功 msg: "OK"// 返回信息消息 data: false // 返回數據,true:數據可用,false:數據不可用 } |
/** * 處理用戶名惟一性驗證請求 * @param param 驗證數據 * @param type 驗證類型 * @param callback 回到函數 * MappingJacksonValue --> 返回json,支持JSONP(實際上是解決JS跨域調用數據的一種方案) * required = false --> 非必須 * @ResponseBody 異步的,不會進行跳轉 */ @RequestMapping("/user/check/{param}/{type}") @ResponseBody public MappingJacksonValue loadPage(@PathVariable String param, @PathVariable Integer type, @RequestParam(required = false) String callback) { EgoResult result = ssoUserService.selectUserByCond(param, type); // 處理json響應數據格式 MappingJacksonValue jacksonValue = new MappingJacksonValue(result); if (!StringUtils.isEmpty(callback)) jacksonValue.setJsonpFunction(callback); return jacksonValue; }
@Service public class SsoUserServiceImpl implements SsoUserService { //注入的是遠程服務的代理對象 @Autowired private UserService userServiceProxy; @Override public EgoResult selectUserByCond(String cond, Integer type) { return userServiceProxy.selectUserByCondService(cond, type); } }
@Service public class UserServiceImpl implements UserService { @Autowired private TbUserMapper tbUserMapper; @Override public EgoResult selectUserByCondService(String cond, Integer type) { //動態產生where條件 TbUserExample example = new TbUserExample(); TbUserExample.Criteria criteria = example.createCriteria(); //封裝查詢條件 if (type.equals(1)) { criteria.andUsernameEqualTo(cond); } else if (type.equals(2)) { criteria.andPhoneEqualTo(cond); } else if (type.equals(3)) { criteria.andEmailEqualTo(cond); } List<TbUser> userList = tbUserMapper.selectByExample(example); // 建立EgoResult對象 EgoResult result = new EgoResult(); result.setStatus(200); result.setMsg("ok"); if (userList!=null && userList.size()>0) result.setData(false); else result.setData(true); // 用戶名可用 return result; } }
請求方法 | POST |
URL | http://sso.egou.com/user/register |
參數說明 | username //用戶名 password //密碼 phone //手機號 email //郵箱 |
示例 | http://sso.egou.com/user/register |
返回值 | { status: 400 msg: "註冊失敗. 請校驗數據後請再提交數據." data: null } |
/** * 實現用戶註冊 * @param user 用戶信息 * @ResponseBody 異步的,不會進行跳轉 */ @RequestMapping(value = "/user/register", method = RequestMethod.POST) @ResponseBody public EgoResult insertUser(TbUser user) { return ssoUserService.insertUserService(user); }
@Service public class SsoUserServiceImpl implements SsoUserService { //注入的是遠程服務的代理對象 @Autowired private UserService userServiceProxy; @Override public EgoResult insertUserService(TbUser user) { // md5加密 String pwd = user.getPassword(); String md5 = DigestUtils.md5DigestAsHex(pwd.getBytes()); user.setPassword(md5); return userServiceProxy.insertUserService(user); } }
@Service public class UserServiceImpl implements UserService { @Autowired private TbUserMapper tbUserMapper; @Override public EgoResult insertUserService(TbUser user) { EgoResult result = new EgoResult(); try { Date date = new Date(); user.setCreated(date); user.setUpdated(date); tbUserMapper.insert(user); result.setStatus(200); result.setMsg("註冊成功."); } catch (Exception e) { result.setStatus(400); result.setMsg("註冊失敗. 請校驗數據後請再提交數據."); e.printStackTrace(); } return result; } }
請求方式 | POST |
URL | htt://sso.eou.com/user/l |
參數說明 | username //用戶名 password //密碼 |
示例 | http://sso.egou.com/user/loginusername=zhangsan&password=123 |
返回值 | { status: 200 msg: "OK" data: "fe5cb546aeb3ce1bf37abcb08a40493e"//登陸成功,返 回 token } |
/** * 實現用戶登陸 * @param username 用戶名 * @param password 密碼 * @ResponseBody 異步的,不會進行跳轉 */ @RequestMapping(value = "/user/login", method = RequestMethod.POST) @ResponseBody public EgoResult userLogin(String username, String password) { return ssoUserService.userLogin(username, password); }
@Service public class UserServiceImpl implements UserService { @Autowired private TbUserMapper tbUserMapper; @Override public TbUser selectUserByUserNameService(String username) { //動態產生where條件 TbUserExample example = new TbUserExample(); TbUserExample.Criteria criteria = example.createCriteria(); //封裝查詢條件 criteria.andUsernameEqualTo(username); // where username=? List<TbUser> userList = tbUserMapper.selectByExample(example); // 由於用戶名惟一 if (userList!=null && userList.size()==1) return userList.get(0); return null; } }
@Service public class SsoUserServiceImpl implements SsoUserService { //注入的是遠程服務的代理對象 @Autowired private UserService userServiceProxy; // 注入JedisCluster集羣訪問對象 @Autowired private JedisCluster jedisCluster; @Override public EgoResult userLogin(String username, String password) { EgoResult result = new EgoResult(); result.setStatus(400); result.setData(null); result.setMsg("用戶名或密碼錯誤."); TbUser tbUser = userServiceProxy.selectUserByUserNameService(username); if (tbUser != null) { //對前端提交的密碼進行加密 password = DigestUtils.md5DigestAsHex(password.getBytes()); if (password.equals(tbUser.getPassword())) { // 將當前登陸用戶對象,轉爲json字符串,保存到redis數據庫 String userJson = JsonUtils.objectToJson(tbUser); String token = UUID.randomUUID().toString(); // 將用戶信息保存到redis數據庫 jedisCluster.set(token, userJson); result.setStatus(200); result.setMsg("登陸成功."); result.setData(token); } } return result; } }