<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于SpringBoot實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

          共 10032字,需瀏覽 21分鐘

           ·

          2020-12-31 18:46

          來源 |? urlify.cn/I3eyAz

          單點(diǎn)登錄系統(tǒng)設(shè)計(jì)思路:采用Spring4 Java配置方式整合HttpClient,Redis ,MySql和SpringBoot的簡(jiǎn)易教程。

          在傳統(tǒng)的系統(tǒng),或者是只有一個(gè)服務(wù)器的系統(tǒng)中。Session在一個(gè)服務(wù)器中,各個(gè)模塊都可以直接獲取,只需登錄一次就進(jìn)入各個(gè)模塊。若在服務(wù)器集群或者是分布式系統(tǒng)架構(gòu)中,每個(gè)服務(wù)器之間的Session并不是共享的,這會(huì)出現(xiàn)每個(gè)模塊都要登錄的情況。這時(shí)候需要通過單點(diǎn)登錄系統(tǒng)(Single Sign On)將用戶信息存在Redis數(shù)據(jù)庫中實(shí)現(xiàn)Session共享的效果。從而實(shí)現(xiàn)一次登錄就可以訪問所有相互信任的應(yīng)用系統(tǒng)。

          一、整合 HttpClient

          HttpClient 是 Apache Jakarta Common 下的子項(xiàng)目,用來提供高效的、最新的、功能豐富的支持 HTTP 協(xié)議的客戶端編程工具包,并且它支持 HTTP 協(xié)議最新的版本和建議。
          首先在src/main/resources 目錄下創(chuàng)建?httpclient.properties?配置文件。

          #設(shè)置整個(gè)連接池默認(rèn)最大連接數(shù)
          http.defaultMaxPerRoute=100
          #設(shè)置整個(gè)連接池最大連接數(shù)
          http.maxTotal=300
          #設(shè)置請(qǐng)求超時(shí)
          http.connectTimeout=1000
          #設(shè)置從連接池中獲取到連接的最長(zhǎng)時(shí)間
          http.connectionRequestTimeout=500
          #設(shè)置數(shù)據(jù)傳輸?shù)淖铋L(zhǎng)時(shí)間
          http.socketTimeout=10000

          然后在 src/main/java/com/itdragon/config 目錄下創(chuàng)建?HttpclientSpringConfig.java?文件
          這里用到了四個(gè)很重要的注解
          @Configuration?: 作用于類上,指明該類就相當(dāng)于一個(gè)xml配置文件
          @Bean?: 作用于方法上,指明該方法相當(dāng)于xml配置中的bean,注意方法名的命名規(guī)范
          @PropertySource?: 指定讀取的配置文件,引入多個(gè)value={“xxx:xxx”,“xxx:xxx”},ignoreResourceNotFound=true 文件不存在時(shí)忽略
          @Value?: 獲取配置文件的值

          package?com.itdragon.config;
          /**
          ?*?@Configuration??作用于類上,相當(dāng)于一個(gè)xml配置文件
          ?*?@Bean????作用于方法上,相當(dāng)于xml配置中的
          ?*?@PropertySource?指定讀取的配置文件,ignoreResourceNotFound=true?文件不存在是忽略
          ?*?@Value???獲取配置文件的值
          ?*/
          @Configuration
          @PropertySource(value?=?"classpath:httpclient.properties",?ignoreResourceNotFound=true)
          public?class?HttpclientSpringConfig?{

          ????@Value("${http.maxTotal}")
          ????private?Integer?httpMaxTotal;

          ????@Value("${http.defaultMaxPerRoute}")
          ????private?Integer?httpDefaultMaxPerRoute;

          ????@Value("${http.connectTimeout}")
          ????private?Integer?httpConnectTimeout;

          ????@Value("${http.connectionRequestTimeout}")
          ????private?Integer?httpConnectionRequestTimeout;

          ????@Value("${http.socketTimeout}")
          ????private?Integer?httpSocketTimeout;

          ????@Autowired
          ????private?PoolingHttpClientConnectionManager?manager;

          ????@Bean
          ????public?PoolingHttpClientConnectionManager?poolingHttpClientConnectionManager()?{
          ????????PoolingHttpClientConnectionManager?poolingHttpClientConnectionManager?=?new?PoolingHttpClientConnectionManager();
          ????????//?最大連接數(shù)
          ????????poolingHttpClientConnectionManager.setMaxTotal(httpMaxTotal);
          ????????//?每個(gè)主機(jī)的最大并發(fā)數(shù)
          ????????poolingHttpClientConnectionManager.setDefaultMaxPerRoute(httpDefaultMaxPerRoute);
          ????????return?poolingHttpClientConnectionManager;
          ????}

          ????@Bean?//?定期清理無效連接
          ????public?IdleConnectionEvictor?idleConnectionEvictor()?{
          ????????return?new?IdleConnectionEvictor(manager,?1L,?TimeUnit.HOURS);
          ????}

          ????@Bean?//?定義HttpClient對(duì)象?注意該對(duì)象需要設(shè)置scope="prototype":多例對(duì)象
          ????@Scope("prototype")
          ????public?CloseableHttpClient?closeableHttpClient()?{
          ????????return?HttpClients.custom().setConnectionManager(this.manager).build();
          ????}

          ????@Bean?//?請(qǐng)求配置
          ????public?RequestConfig?requestConfig()?{
          ????????return?RequestConfig.custom().setConnectTimeout(httpConnectTimeout)?//?創(chuàng)建連接的最長(zhǎng)時(shí)間
          ????????????????.setConnectionRequestTimeout(httpConnectionRequestTimeout)?//?從連接池中獲取到連接的最長(zhǎng)時(shí)間
          ????????????????.setSocketTimeout(httpSocketTimeout)?//?數(shù)據(jù)傳輸?shù)淖铋L(zhǎng)時(shí)間
          ????????????????.build();
          ????}

          }

          二、整合 Redis

          SpringBoot官方其實(shí)提供了spring-boot-starter-redis pom 幫助我們快速開發(fā),但我們也可以自定義配置,這樣可以更方便地掌控。
          首先在src/main/resources 目錄下創(chuàng)建 redis.properties 配置文件

          redis.maxTotal=200
          redis.node.host=10.128.15.21
          redis.node.port=6379
          REDIS_USER_SESSION_KEY=REDIS_USER_SESSION
          SSO_SESSION_EXPIRE=30

          設(shè)置Redis主機(jī)的ip地址和端口號(hào),和存入Redis數(shù)據(jù)庫中的key以及存活時(shí)間。這里為了方便測(cè)試,存活時(shí)間設(shè)置的比較小。這里的配置是單例Redis。
          在src/main/java/com/itdragon/config 目錄下創(chuàng)建?RedisSpringConfig.java?文件。

          @Configuration
          @PropertySource(value?=?"classpath:redis.properties")
          public?class?RedisSpringConfig?{

          ????@Value("${redis.maxTotal}")
          ????private?Integer?redisMaxTotal;

          ????@Value("${redis.node.host}")
          ????private?String?redisNodeHost;

          ????@Value("${redis.node.port}")
          ????private?Integer?redisNodePort;

          ????private?JedisPoolConfig?jedisPoolConfig()?{
          ????????JedisPoolConfig?jedisPoolConfig?=?new?JedisPoolConfig();
          ????????jedisPoolConfig.setMaxTotal(redisMaxTotal);
          ????????return?jedisPoolConfig;
          ????}
          ????
          ????@Bean?
          ????public?JedisPool?getJedisPool(){?//?省略第一個(gè)參數(shù)則是采用?Protocol.DEFAULT_DATABASE
          ?????JedisPool?jedisPool?=?new?JedisPool(jedisPoolConfig(),?redisNodeHost,?redisNodePort);
          ????????return?jedisPool;
          ????}

          ????@Bean
          ????public?ShardedJedisPool?shardedJedisPool()?{
          ????????List?jedisShardInfos?=?new?ArrayList();
          ????????jedisShardInfos.add(new?JedisShardInfo(redisNodeHost,?redisNodePort));
          ????????return?new?ShardedJedisPool(jedisPoolConfig(),?jedisShardInfos);
          ????}
          }

          三、Service 層

          在src/main/java/com/itdragon/service 目錄下創(chuàng)建 UserService.java 文件,它負(fù)責(zé)三件事情
          第一件事情:驗(yàn)證用戶信息是否正確,并將登錄成功的用戶信息保存到Redis數(shù)據(jù)庫中。
          第二件事情:負(fù)責(zé)判斷用戶令牌是否過期,若沒有則刷新令牌存活時(shí)間。
          第三件事情:負(fù)責(zé)從Redis數(shù)據(jù)庫中刪除用戶信息。

          package?com.itdragon.service;

          @Service
          @Transactional
          @PropertySource(value?=?"classpath:redis.properties")
          public?class?UserService?{
          ?@Autowired
          ?private?UserRepository?userRepository;
          ?@Autowired
          ?private?JedisClient?jedisClient;
          ?@Value("${REDIS_USER_SESSION_KEY}")
          ?private?String?REDIS_USER_SESSION_KEY;
          ?@Value("${SSO_SESSION_EXPIRE}")
          ?private?Integer?SSO_SESSION_EXPIRE;
          ?
          ????public?Result?registerUser(User?user)?{
          ?????//?檢查用戶名是否注冊(cè),一般在前端驗(yàn)證的時(shí)候處理,因?yàn)樽?cè)不存在高并發(fā)的情況,這里再加一層查詢是不影響性能的
          ?????if?(null?!=?userRepository.findByAccount(user.getAccount()))?{
          ??????return?Result.build(400,?"");
          ?????}
          ?????userRepository.save(user);
          ?????//?注冊(cè)成功后選擇發(fā)送郵件激活。現(xiàn)在一般都是短信驗(yàn)證碼
          ?????return?Result.build(200,?"");
          ????}
          ????
          ????public?Result?userLogin(String?account,?String?password,
          ???????HttpServletRequest?request,?HttpServletResponse?response)?{
          ?????//?判斷賬號(hào)密碼是否正確
          ??User?user?=?userRepository.findByAccount(account);
          ??if(user?==?null){
          ???return?Result.build(400,?"賬號(hào)名或密碼錯(cuò)誤");
          ??}
          ??if?(!CheckUtils.decryptPassword(user,?password))?{
          ???return?Result.build(400,?"賬號(hào)名或密碼錯(cuò)誤");
          ??}
          ??//?生成token
          ??String?token?=?UUID.randomUUID().toString();
          ??//?清空密碼和鹽避免泄漏
          ??String?userPassword?=?user.getPassword();
          ??String?userSalt?=?user.getSalt();
          ??user.setPassword(null);
          ??user.setSalt(null);
          ??//?把用戶信息寫入?redis
          ??jedisClient.set(REDIS_USER_SESSION_KEY?+?":"?+?token,?JsonUtils.objectToJson(user));
          ??// user 已經(jīng)是持久化對(duì)象了,被保存在了session緩存當(dāng)中,若user又重新修改了屬性值,那么在提交事務(wù)時(shí),此時(shí) hibernate對(duì)象就會(huì)拿當(dāng)前這個(gè)user對(duì)象和保存在session緩存中的user對(duì)象進(jìn)行比較,如果兩個(gè)對(duì)象相同,則不會(huì)發(fā)送update語句,否則,如果兩個(gè)對(duì)象不同,則會(huì)發(fā)出update語句。
          ??user.setPassword(userPassword);
          ??user.setSalt(userSalt);
          ??//?設(shè)置?session?的過期時(shí)間
          ??jedisClient.expire(REDIS_USER_SESSION_KEY?+?":"?+?token,?SSO_SESSION_EXPIRE);
          ??//?添加寫 cookie 的邏輯,cookie 的有效期是關(guān)閉瀏覽器就失效。
          ??CookieUtils.setCookie(request,?response,?"USER_TOKEN",?token);
          ??//?返回token
          ??return?Result.ok(token);
          ?}
          ????
          ????public?void?logout(String?token)?{
          ?????jedisClient.del(REDIS_USER_SESSION_KEY?+?":"?+?token);
          ????}

          ?public?Result?queryUserByToken(String?token)?{
          ??//?根據(jù)token從redis中查詢用戶信息
          ??String?json?=?jedisClient.get(REDIS_USER_SESSION_KEY?+?":"?+?token);
          ??//?判斷是否為空
          ??if?(StringUtils.isEmpty(json))?{
          ???return?Result.build(400,?"此session已經(jīng)過期,請(qǐng)重新登錄");
          ??}
          ??//?更新過期時(shí)間
          ??jedisClient.expire(REDIS_USER_SESSION_KEY?+?":"?+?token,?SSO_SESSION_EXPIRE);
          ??//?返回用戶信息
          ??return?Result.ok(JsonUtils.jsonToPojo(json,?User.class));
          ?}
          }

          四、Controller 層

          負(fù)責(zé)跳轉(zhuǎn)登錄頁面跳轉(zhuǎn),負(fù)責(zé)用戶的登錄,退出,獲取令牌的操作。UserController.java和PageController.java

          package?com.itdragon.controller;

          @Controller
          @RequestMapping("/user")
          public?class?UserController?{
          ?@Autowired
          ?private?UserService?userService;
          ?@RequestMapping(value="/login",?method=RequestMethod.POST)
          ?@ResponseBody
          ?public?Result?userLogin(String?username,?String?password,
          ????????????????????????????HttpServletRequest?request,?HttpServletResponse?response)?{
          ??try?{
          ???Result?result?=?userService.userLogin(username,?password,?request,?response);
          ???return?result;
          ??}?catch?(Exception?e)?{
          ???e.printStackTrace();
          ???return?Result.build(500,?"");
          ??}
          ?}
          ?
          ?@RequestMapping(value="/logout/{token}")
          ?public?String?logout(@PathVariable?String?token)?{
          ??userService.logout(token);?//?思路是從Redis中刪除key,實(shí)際情況請(qǐng)和業(yè)務(wù)邏輯結(jié)合
          ??return?"back";
          ?}
          ?
          ?@RequestMapping("/token/{token}")
          ?@ResponseBody
          ?public?Object?getUserByToken(@PathVariable?String?token)?{
          ??Result?result?=?null;
          ??try?{
          ???result?=?userService.queryUserByToken(token);
          ??}?catch?(Exception?e)?{
          ???e.printStackTrace();
          ???result?=?Result.build(500,?"");
          ??}
          ??return?result;
          ?}
          }

          package?com.itdragon.controller;

          @Controller
          public?class?PageController?{
          ?@RequestMapping("/login")
          ?public?String?showLogin(String?redirect,?Model?model)?{
          ??model.addAttribute("redirect",?redirect);
          ??return?"login";
          ?}?
          }

          五、視圖層

          一個(gè)簡(jiǎn)單的登錄頁面和資源展示頁面。login.jsp、index.jsp和indexHomePage.jsp

          六、Spring 自定義攔截器

          這里是另外一個(gè)項(xiàng)目 service-test-sso 中的代碼,首先在src/main/resources/spring/springmvc.xml 中配置攔截器,設(shè)置哪些請(qǐng)求需要攔截

          ?"com.it.controller"?/>
          ?
          ???class="org.springframework.web.servlet.view.InternalResourceViewResolver">
          ??"prefix"?value="/WEB-INF/views/"?/>
          ??"suffix"?value=".jsp"?/>
          ?
          ?
          ?"/WEB-INF/static/"?mapping="/static/**"/>
          ?
          ?
          ??
          ???"/indexHomePage/**"/>
          ???"com.it.interceptors.UserLoginHandlerInterceptor"/>
          ??

          ?


          UserLoginHandlerInterceptor.java

          package?com.it.interceptors;

          public?class?UserLoginHandlerInterceptor?implements?HandlerInterceptor?{

          ????public?static?final?String?COOKIE_NAME?=?"USER_TOKEN";
          ????@Autowired
          ????private?UserService?userService;
          ????@Override
          ????public?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler)
          ????????????throws?Exception?{
          ????????String?token?=?CookieUtils.getCookieValue(request,?COOKIE_NAME);
          ????????User?user?=?this.userService.getUserByToken(token);
          ????????if?(StringUtils.isEmpty(token)?||?null?==?user)?{
          ???//?跳轉(zhuǎn)到登錄頁面,把用戶請(qǐng)求的url作為參數(shù)傳遞給登錄頁面。
          ???response.sendRedirect("http://localhost:8081/login?redirect="?+?request.getRequestURL());
          ???//?返回false
          ???return?false;
          ??}
          ??//?把用戶信息放入Request
          ??request.setAttribute("user",?user);
          ??//?返回值決定handler是否執(zhí)行。true:執(zhí)行,false:不執(zhí)行。
          ??return?true;
          ????}

          ????@Override
          ????public?void?postHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,
          ????????????ModelAndView?modelAndView)?throws?Exception?{
          ????}

          ????@Override
          ????public?void?afterCompletion(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,
          ????????????Exception?ex)?throws?Exception?{
          ????}

          }

          七、操作步驟

          測(cè)試思路:
          第一步:注冊(cè)用戶,執(zhí)行sso 項(xiàng)目下SpringbootStudyApplicationTests.java 單元測(cè)試類中的 registerUser() 方法添加用戶。
          第二步:開啟sso服務(wù)。
          第三步:再開啟兩個(gè)service-test-sso服務(wù)。
          第四步:在service-test-sso服務(wù)頁面點(diǎn)擊“訪問主頁”按鈕進(jìn)入權(quán)限頁面測(cè)試。

          八、sso項(xiàng)目結(jié)構(gòu)

          service-test-sso項(xiàng)目結(jié)構(gòu)? ?

          訪問主頁

          點(diǎn)擊登錄

          用戶表存儲(chǔ)如下

          依次通過訪問如下鏈接:
          http://localhost:8083/
          http://localhost:8081/login?redirect=/indexHomePage
          http://localhost:8082/
          然后直接就可以不用登錄就可以訪問資源了,實(shí)現(xiàn)SSO功能


          推薦閱讀

          GitHub 下載神器強(qiáng)勢(shì)回歸!

          巧用枚舉來干掉if-else,代碼更優(yōu)雅!

          如何正確訪問Redis中的海量數(shù)據(jù)?服務(wù)才不會(huì)掛掉!

          超硬核!1.6W 字 Redis 面試知識(shí)點(diǎn)總結(jié),建議收藏!

          瀏覽 70
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天日天天操天天 | 欧美黄片免费播放 | 日本黄色片一级视频 | 久操综合视频在线 | 三级在线视频播放 |