日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當前位置:首頁 > > 架構(gòu)師社區(qū)
[導讀]單點登錄系統(tǒng)設計思路:采用Spring4 Java配置方式整合HttpClient,Redis ,MySql和SpringBoot的簡易教程。

來源: urlify.cn/I3eyAz


單點登錄系統(tǒng)設計思路:采用Spring4 Java配置方式整合HttpClient,Redis ,MySql和SpringBoot的簡易教程。
基于SpringBoot實現(xiàn)單點登錄系統(tǒng)
在傳統(tǒng)的系統(tǒng),或者是只有一個服務器的系統(tǒng)中。Session在一個服務器中,各個模塊都可以直接獲取,只需登錄一次就進入各個模塊。若在服務器集群或者是分布式系統(tǒng)架構(gòu)中,每個服務器之間的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() {
        ListjedisShardInfos = 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ā)送郵件激活?,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 已經(jīng)是持久化對象了,被保存在了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已經(jīng)過期,請重新登錄");
  }
  // 更新過期時間
  jedisClient.expire(REDIS_USER_SESSION_KEY + ":" + token, SSO_SESSION_EXPIRE);
  // 返回用戶信息 return Result.ok(JsonUtils.jsonToPojo(json, User.class));
 }
}

四、Controller 層

負責跳轉(zhuǎn)登錄頁面跳轉(zhuǎn),負責用戶的登錄,退出,獲取令牌的操作。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è)務邏輯結(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";
 } 
}

五、視圖層

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

六、Spring 自定義攔截器

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

"com.it.controller" />"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)到登錄頁面,把用戶請求的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服務頁面點擊“訪問主頁”按鈕進入權(quán)限頁面測試。

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

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

service-test-sso項目結(jié)構(gòu)
基于SpringBoot實現(xiàn)單點登錄系統(tǒng)

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

點擊登錄
基于SpringBoot實現(xiàn)單點登錄系統(tǒng)
用戶表存儲如下
基于SpringBoot實現(xiàn)單點登錄系統(tǒng)
依次通過訪問如下鏈接:
http://localhost:8083/
http://localhost:8081/login?redirect=/indexHomePage
http://localhost:8082/
然后直接就可以不用登錄就可以訪問資源了,實現(xiàn)SSO功能


免責聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關鍵字: 驅(qū)動電源

在工業(yè)自動化蓬勃發(fā)展的當下,工業(yè)電機作為核心動力設備,其驅(qū)動電源的性能直接關系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅(qū)動電源設計中至關重要的兩個環(huán)節(jié),集成化方案的設計成為提升電機驅(qū)動性能的關鍵。

關鍵字: 工業(yè)電機 驅(qū)動電源

LED 驅(qū)動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設備的使用壽命。然而,在實際應用中,LED 驅(qū)動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設計、生...

關鍵字: 驅(qū)動電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動電源的公式,電感內(nèi)電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關鍵字: LED 設計 驅(qū)動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動汽車的核心技術之一是電機驅(qū)動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅(qū)動系統(tǒng)中的關鍵元件,其性能直接影響到電動汽車的動力性能和...

關鍵字: 電動汽車 新能源 驅(qū)動電源

在現(xiàn)代城市建設中,街道及停車場照明作為基礎設施的重要組成部分,其質(zhì)量和效率直接關系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關鍵字: 發(fā)光二極管 驅(qū)動電源 LED

LED通用照明設計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關鍵字: LED 驅(qū)動電源 功率因數(shù)校正

在LED照明技術日益普及的今天,LED驅(qū)動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關鍵字: LED照明技術 電磁干擾 驅(qū)動電源

開關電源具有效率高的特性,而且開關電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅(qū)動電源

關鍵字: LED 驅(qū)動電源 開關電源

LED驅(qū)動電源是把電源供應轉(zhuǎn)換為特定的電壓電流以驅(qū)動LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關鍵字: LED 隧道燈 驅(qū)動電源
關閉