1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 10043字,需瀏覽 21分鐘

         ·

        2020-12-18 15:22

        點擊上方藍色字體,選擇“標星公眾號”

        優(yōu)質文章,第一時間送達

        ? 作者?|??愛學習的華華

        來源 |? urlify.cn/I3eyAz

        66套java從入門到精通實戰(zhàn)課程分享

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

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

        一、整合 HttpClient

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

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

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

        package?com.itdragon.config;
        /**
        ?*?@Configuration??作用于類上,相當于一個xml配置文件
        ?*?@Bean????作用于方法上,相當于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);
        ????????//?每個主機的最大并發(fā)數(shù)
        ????????poolingHttpClientConnectionManager.setDefaultMaxPerRoute(httpDefaultMaxPerRoute);
        ????????return?poolingHttpClientConnectionManager;
        ????}

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

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

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

        }

        二、整合 Redis

        SpringBoot官方其實提供了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

        設置Redis主機的ip地址和端口號,和存入Redis數(shù)據(jù)庫中的key以及存活時間。這里為了方便測試,存活時間設置的比較小。這里的配置是單例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(){?//?省略第一個參數(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 文件,它負責三件事情
        第一件事情:驗證用戶信息是否正確,并將登錄成功的用戶信息保存到Redis數(shù)據(jù)庫中。
        第二件事情:負責判斷用戶令牌是否過期,若沒有則刷新令牌存活時間。
        第三件事情:負責從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)?{
        ?????//?檢查用戶名是否注冊,一般在前端驗證的時候處理,因為注冊不存在高并發(fā)的情況,這里再加一層查詢是不影響性能的
        ?????if?(null?!=?userRepository.findByAccount(user.getAccount()))?{
        ??????return?Result.build(400,?"");
        ?????}
        ?????userRepository.save(user);
        ?????//?注冊成功后選擇發(fā)送郵件激活。現(xiàn)在一般都是短信驗證碼
        ?????return?Result.build(200,?"");
        ????}
        ????
        ????public?Result?userLogin(String?account,?String?password,
        ???????HttpServletRequest?request,?HttpServletResponse?response)?{
        ?????//?判斷賬號密碼是否正確
        ??User?user?=?userRepository.findByAccount(account);
        ??if(user?==?null){
        ???return?Result.build(400,?"賬號名或密碼錯誤");
        ??}
        ??if?(!CheckUtils.decryptPassword(user,?password))?{
        ???return?Result.build(400,?"賬號名或密碼錯誤");
        ??}
        ??//?生成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 已經是持久化對象了,被保存在了session緩存當中,若user又重新修改了屬性值,那么在提交事務時,此時 hibernate對象就會拿當前這個user對象和保存在session緩存中的user對象進行比較,如果兩個對象相同,則不會發(fā)送update語句,否則,如果兩個對象不同,則會發(fā)出update語句。
        ??user.setPassword(userPassword);
        ??user.setSalt(userSalt);
        ??//?設置?session?的過期時間
        ??jedisClient.expire(REDIS_USER_SESSION_KEY?+?":"?+?token,?SSO_SESSION_EXPIRE);
        ??//?添加寫 cookie 的邏輯,cookie 的有效期是關閉瀏覽器就失效。
        ??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已經過期,請重新登錄");
        ??}
        ??//?更新過期時間
        ??jedisClient.expire(REDIS_USER_SESSION_KEY?+?":"?+?token,?SSO_SESSION_EXPIRE);
        ??//?返回用戶信息
        ??return?Result.ok(JsonUtils.jsonToPojo(json,?User.class));
        ?}
        }

        四、Controller 層

        負責跳轉登錄頁面跳轉,負責用戶的登錄,退出,獲取令牌的操作。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,實際情況請和業(yè)務邏輯結合
        ??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";
        ?}?
        }

        五、視圖層

        一個簡單的登錄頁面和資源展示頁面。login.jsp、index.jsp和indexHomePage.jsp

        六、Spring 自定義攔截器

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

        ?"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)?{
        ???//?跳轉到登錄頁面,把用戶請求的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?{
        ????}

        }

        七、操作步驟

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

        八、sso項目結構

        service-test-sso項目結構? ?

        訪問主頁

        點擊登錄

        用戶表存儲如下

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




        粉絲福利:Java從入門到入土學習路線圖

        ???

        ?長按上方微信二維碼?2 秒


        感謝點贊支持下哈?

        瀏覽 30
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            1一325女s调教女mvk | 色情视频在线观看 | 色情网插比片 | 天堂新版8中文在线8 | 国产露脸对白88av | 国产黄色小视频在线观看 | 成人秘 高潮片免费视频在线观看 | 极品少妇馒头泬99 | 操女人av天天 | 天天操夜夜拍免费视频 |