溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

發(fā)布時(shí)間:2021-09-04 09:13:18 來(lái)源:億速云 閱讀:162 作者:chen 欄目:開(kāi)發(fā)技術(shù)

本篇內(nèi)容介紹了“利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

目錄
  • 1.單點(diǎn)登錄系統(tǒng)介紹

  • 2.簡(jiǎn)單業(yè)務(wù)實(shí)現(xiàn)

    • 2.1添加依賴

    • 2.2 項(xiàng)目配置文件

    • 2.3添加項(xiàng)目啟動(dòng)類(lèi)

    • 2.4 啟動(dòng)并訪問(wèn)項(xiàng)目

  • 3. 優(yōu)化進(jìn)一步設(shè)計(jì)

    • 3.1 定義安全配置類(lèi) SecurityConfig

    • 3.2定義用戶信息處理對(duì)象

    • 3.3 網(wǎng)關(guān)中登陸路由配置

    • 3.4基于Postman進(jìn)行訪問(wèn)測(cè)試

    • 3.5 定義登陸頁(yè)面

    • 3.6 構(gòu)建令牌配置對(duì)象

    • 3.7 定義認(rèn)證授權(quán)核心配置

      • 授權(quán)服務(wù)器的核心配置

      • Postman訪問(wèn)測(cè)試

  • 4 資源服務(wù)器配置–sca-resource

    • 4.1 構(gòu)建令牌配置對(duì)象

      • 4.3 設(shè)置資源訪問(wèn)的權(quán)限

        • 4.4 啟動(dòng)服務(wù)訪問(wèn)測(cè)試

          • 4.4.1 訪問(wèn)auth認(rèn)證授權(quán)服務(wù)獲取token

          • 4.4.2 攜帶TOKEN訪問(wèn)資源服務(wù)器

          • 4.4.3 對(duì)403異常前端頁(yè)面顯示

        • 4.5 Oauth3規(guī)范

          • 4.6 面試問(wèn)題點(diǎn)

          • 5 Bug 分析

            1.單點(diǎn)登錄系統(tǒng)介紹

            多點(diǎn)登陸系統(tǒng)。應(yīng)用起來(lái)相對(duì)繁瑣(每次訪問(wèn)資源服務(wù)都需要重新登陸認(rèn)證和授權(quán))。與此同時(shí),系統(tǒng)代碼的重復(fù)也比較高。所以單點(diǎn)登錄系統(tǒng),倍受歡迎!
            單點(diǎn)登錄系統(tǒng),即多個(gè)站點(diǎn)共用一臺(tái)認(rèn)證授權(quán)服務(wù)器,用戶在其中任何一個(gè)站點(diǎn)登錄后,可以免登錄訪問(wèn)其他所有站點(diǎn)。而且,各站點(diǎn)間可以通過(guò)該登錄狀態(tài)直接交互。

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            2.簡(jiǎn)單業(yè)務(wù)實(shí)現(xiàn)

            在文件上傳的項(xiàng)目添加認(rèn)證授權(quán)服務(wù),義登錄頁(yè)面(login.html),然后在頁(yè)面中輸入自己的登陸賬號(hào),登陸密碼,將請(qǐng)求提交給網(wǎng)關(guān),然后網(wǎng)關(guān)將請(qǐng)求轉(zhuǎn)發(fā)到auth工程,登陸成功和失敗要返回json數(shù)據(jù),按照如下結(jié)構(gòu)實(shí)現(xiàn)

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            在02-sca工程創(chuàng)建 sca-auth子module,作為認(rèn)證授權(quán)服務(wù)

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            2.1添加依賴

            <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-web</artifactId>
                    </dependency>
                    <dependency>
                        <groupId>com.alibaba.cloud</groupId>
                        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                    </dependency>
                    <dependency>
                        <groupId>com.alibaba.cloud</groupId>
                        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
                    </dependency>
                    <dependency>
                        <groupId>org.springframework.cloud</groupId>
                        <artifactId>spring-cloud-starter-oauth3</artifactId>
                    </dependency>
                    <dependency>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                    </dependency>

            2.2 項(xiàng)目配置文件

            在sca-auth工程中創(chuàng)建bootstrap.yml文件

            server:
              port: 8071
            spring:
              application:
                name: sca-auth
              cloud:
                nacos:
                  discovery:
                    server-addr: localhost:8848
                  config:
                    server-addr: localhost:8848

            2.3添加項(xiàng)目啟動(dòng)類(lèi)

            package com.jt;
            
            import org.springframework.boot.SpringApplication;
            import org.springframework.boot.autoconfigure.SpringBootApplication;
            
            @SpringBootApplication
            public class ResourceAuthApplication {
                public static void main(String[] args) {
                    SpringApplication.run(ResourceAuthApplication.class, args);
                }
            }

            2.4 啟動(dòng)并訪問(wèn)項(xiàng)目

            項(xiàng)目啟動(dòng)時(shí),系統(tǒng)會(huì)默認(rèn)生成一個(gè)登陸密碼

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            打開(kāi)瀏覽器輸入http://localhost:8071呈現(xiàn)登陸頁(yè)面

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            默認(rèn)用戶名為user,密碼為系統(tǒng)啟動(dòng)時(shí),在控制臺(tái)呈現(xiàn)的密碼。執(zhí)行登陸測(cè)試,登陸成功進(jìn)入如下界面(因?yàn)闆](méi)有定義登陸頁(yè)面,所以會(huì)出現(xiàn)404)

            3. 優(yōu)化進(jìn)一步設(shè)計(jì)

            3.1 定義安全配置類(lèi) SecurityConfig

            修改SecurityConfig配置類(lèi),添加登錄成功或失敗的處理邏輯

            package com.jt.auth.config;
            
            import com.fasterxml.jackson.databind.ObjectMapper;
            import org.springframework.context.annotation.Bean;
            import org.springframework.context.annotation.Configuration;
            import org.springframework.security.authentication.AuthenticationManager;
            import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
            import org.springframework.security.config.annotation.web.builders.HttpSecurity;
            import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
            import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
            import org.springframework.security.web.authentication.AuthenticationFailureHandler;
            import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
            
            import javax.servlet.http.HttpServletResponse;
            import java.io.IOException;
            import java.io.PrintWriter;
            import java.util.HashMap;
            import java.util.Map;
            
            @Configuration//配置對(duì)象--系統(tǒng)啟動(dòng)時(shí)底層會(huì)產(chǎn)生代理對(duì)象,來(lái)初始化一些對(duì)象
            public class SecurityConfig extends WebSecurityConfigurerAdapter {
            //WebSecurityConfigurerAdapter 類(lèi)是個(gè)適配器, 在配置的時(shí)候,需要我們自己寫(xiě)個(gè)配置類(lèi)去繼承他,然后編寫(xiě)自己所特殊需要的配置
                //BCryptPasswordEncoder密碼加密對(duì)象比MD5安全性更高,MD5暴力反射可以破解過(guò)
                @Bean
                public BCryptPasswordEncoder passwordEncoder(){
                    return new BCryptPasswordEncoder();
                }
            
                /**
                 * 配置認(rèn)證管理器(負(fù)責(zé)對(duì)客戶輸入的用戶信息進(jìn)行認(rèn)證),在其他配置類(lèi)中會(huì)用到這個(gè)對(duì)象
                 * @return
                 * @throws Exception
                 */
                @Bean
                public AuthenticationManager authenticationManagerBean()
                        throws Exception {
                    return super.authenticationManagerBean();
                }
            
                /**在這個(gè)方法中定義登錄規(guī)則
                 * 1)對(duì)所有請(qǐng)求放行(當(dāng)前工程只做認(rèn)證)
                 * 2)登錄成功信息的返回
                 * 3)登錄失敗信息的返回
                 * */
                @Override
                protected void configure(HttpSecurity http) throws Exception {
                    //禁用跨域
                    http.csrf().disable();
                    //放行所有請(qǐng)求
                    http.authorizeRequests().anyRequest().permitAll();
                    //登錄成功與失敗的處理
                    http.formLogin()
                            .successHandler(successHandler()) // .successHandler(AuthenticationSuccessHandler對(duì)象)
                            .failureHandler(failureHandler());
                }
            
                @Bean
                //構(gòu)建successHandler()方法來(lái)創(chuàng)建AuthenticationSuccessHandler對(duì)象
                public AuthenticationSuccessHandler successHandler(){
            //        return new AuthenticationSuccessHandler() {
            //            @Override
            //            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
            //
            //            }
            //        }
                    return (request,response,authentication) ->{
                        //1.構(gòu)建map對(duì)象,封裝響應(yīng)數(shù)據(jù)
                        Map<String,Object> map=new HashMap<>();
                        map.put("state",200);
                        map.put("message","login ok"); //登錄成功返回的響應(yīng)信息
                        //2.將map對(duì)象寫(xiě)到客戶端
                        writeJsonToClient(response,map);
                    };
                }
                @Bean
                //failureHandler();方法來(lái)創(chuàng)建AuthenticationSuccessHandler對(duì)象
                public AuthenticationFailureHandler failureHandler(){
                    return (request,response, e)-> {
                        //1.構(gòu)建map對(duì)象,封裝響應(yīng)數(shù)據(jù)
                        Map<String,Object> map=new HashMap<>();
                        map.put("state",500);
                        map.put("message","login failure");//登錄失敗返回的響應(yīng)信息
                        //2.將map對(duì)象寫(xiě)到客戶端
                        writeJsonToClient(response,map);
                    };
                }
                //提取公共代碼,將對(duì)象轉(zhuǎn)為Json傳給客戶端, 構(gòu)建writeJsonToClient();
                private void writeJsonToClient(HttpServletResponse response,
                                               Object object) throws IOException { // Object 類(lèi)型,不只是Map類(lèi)型,說(shuō)不準(zhǔn)
                    //2.將對(duì)象轉(zhuǎn)換為json
                    //Gson-->toJson  (需要自己找依賴)
                    //fastjson-->JSON (spring-cloud-starter-alibaba-sentinel)
                    //jackson-->writeValueAsString (spring-boot-starter-web)
                    String jsonStr=new ObjectMapper().writeValueAsString(object);
                    //3.將json字符串寫(xiě)到客戶端
                    PrintWriter writer = response.getWriter();
                    writer.println(jsonStr);
                    writer.flush();
                }
            }

            3.2定義用戶信息處理對(duì)象

            正常來(lái)說(shuō),用來(lái)與數(shù)據(jù)庫(kù)中的用戶信息作對(duì)比,認(rèn)證是否正確,可否授權(quán)

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            package com.jt.auth.service;
            
            import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.security.core.GrantedAuthority;
            import org.springframework.security.core.authority.AuthorityUtils;
            import org.springframework.security.core.userdetails.User;
            import org.springframework.security.core.userdetails.UserDetails;
            import org.springframework.security.core.userdetails.UserDetailsService;
            import org.springframework.security.core.userdetails.UsernameNotFoundException;
            import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
            import org.springframework.stereotype.Service;
            
            import java.util.List;
            
            /**
             * 登錄時(shí)用戶信息的獲取和封裝會(huì)在此對(duì)象進(jìn)行實(shí)現(xiàn),
             * 在頁(yè)面上點(diǎn)擊登錄按鈕時(shí),會(huì)調(diào)用這個(gè)對(duì)象的loadUserByUsername方法,
             * 頁(yè)面上輸入的用戶名會(huì)傳給這個(gè)方法的參數(shù)
             *
             */
            @Service
            public class UserDetailsServiceImpl implements UserDetailsService {//獲取用戶詳細(xì)信息的接口
                @Autowired
                //BCryptPasswordEncoder密碼加密對(duì)象
                private BCryptPasswordEncoder passwordEncoder;
                //UserDetails用戶封裝用戶信息(認(rèn)證和權(quán)限信息)
                @Override
                //重寫(xiě)UserDetailsService 接口中的 loadUserByUsername();方法,定義用來(lái)核對(duì)數(shù)據(jù)庫(kù)數(shù)據(jù)和授于相應(yīng)的權(quán)限
                public UserDetails loadUserByUsername(String username)
                        throws UsernameNotFoundException {
                    //1.基于用戶名查詢用戶信息(用戶名,用戶狀態(tài),密碼,....)
                    //Userinfo userinfo=userMapper.selectUserByUsername(username);數(shù)據(jù)庫(kù)用戶信息查詢操作簡(jiǎn)寫(xiě)了
                    String encodedPassword=passwordEncoder.encode("123456");
                    //2.查詢用戶權(quán)限信息(后面訪問(wèn)數(shù)據(jù)庫(kù))
                    //這里先給幾個(gè)假數(shù)據(jù)
                    List<GrantedAuthority> authorities =
                            AuthorityUtils.createAuthorityList(//這里的權(quán)限信息先這么寫(xiě),后面講
                                    "sys:res:create", "sys:res:retrieve");
                    //3.對(duì)用戶信息進(jìn)行封裝
                    return new User(username,encodedPassword,authorities);
                }
            }

            3.3 網(wǎng)關(guān)中登陸路由配置

            在網(wǎng)關(guān)配置文件中添加如下配置

            server:
              port: 9001
            spring:
              application:
                name: sca-resource-gateway
              cloud:
                sentinel: #限流設(shè)計(jì)
                  transport:
                    dashboard: localhost:8180
                  eager: true
                nacos:
                  discovery:
                    server-addr: localhost:8848
                  config:
                    server-addr: localhost:8848
                    file-extension: yml
                gateway:
                  discovery:
                    locator:
                      enabled: true
                  routes:
                    - id: router02
                      uri: lb://sca-auth  #lb表示負(fù)載均衡,底層默認(rèn)使用ribbon實(shí)現(xiàn)
                      predicates: #定義請(qǐng)求規(guī)則(請(qǐng)求需要按照此規(guī)則設(shè)計(jì))
                        - Path=/auth/login/** #請(qǐng)求路徑設(shè)計(jì),單體架構(gòu)
                      filters:
                        - StripPrefix=1 #轉(zhuǎn)發(fā)之前去掉path中第一層路徑

            3.4基于Postman進(jìn)行訪問(wèn)測(cè)試

            啟動(dòng)sca-gateway,sca-auth服務(wù),然后基于postman進(jìn)行訪問(wèn)測(cè)試

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            3.5 定義登陸頁(yè)面

            在sca-resource-ui工程的static目錄中定義login-sso.html 登陸頁(yè)面

            <!doctype html>
            <html lang="en">
            <head>
                <!-- Required meta tags -->
                <meta charset="utf-8">
                <meta name="viewport" content="width=device-width, initial-scale=1">
                <!-- Bootstrap CSS -->
                <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="external nofollow"  rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
                <title>login</title>
            </head>
            <body>
            <div class="container"id="app">
                <h4>Please Login</h4>
                <form>
                    <div class="mb-3">
                        <label for="usernameId" class="form-label">Username</label>
                        <input type="text" v-model="username" class="form-control" id="usernameId" aria-describedby="emailHelp">
                    </div>
                    <div class="mb-3">
                        <label for="passwordId" class="form-label">Password</label>
                        <input type="password" v-model="password" class="form-control" id="passwordId">
                    </div>
                    <button type="button" @click="doLogin()" class="btn btn-primary">Submit</button>
                </form>
            </div>
            <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
            <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
            <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
            <script>
                var vm=new Vue({
                    el:"#app",//定義監(jiān)控點(diǎn),vue底層會(huì)基于此監(jiān)控點(diǎn)在內(nèi)存中構(gòu)建dom樹(shù)
                    data:{ //此對(duì)象中定義頁(yè)面上要操作的數(shù)據(jù)
                        username:"",
                        password:""
                    },
                    methods: {//此位置定義所有業(yè)務(wù)事件處理函數(shù)
                        doLogin() {
                            //1.定義url
                            let url = "http://localhost:9001/auth/oauth/token"
                            //2.定義參數(shù)
            
                            let params = new URLSearchParams()
                            params.append('username',this.username);
                            params.append('password',this.password);
                            params.append("client_id","gateway-client");
                            params.append("grant_type","password");
                            params.append("client_secret","123456");
            
                            //3.發(fā)送異步請(qǐng)求
                            axios.post(url, params).then((response) => {
                                debugger
                                console.log(response.data);
                                let result=response.data;
                                //
                                localStorage.setItem("accessToken",result.access_token);
                                location.href="/fileupload.html" rel="external nofollow" 
            
                            })
                        }
                    }
                });
            </script>
            </body>
            </html>

            3.6 構(gòu)建令牌配置對(duì)象

            借助JWT(Json Web Token-是一種json格式)方式將用戶信息轉(zhuǎn)換為json格式,然后進(jìn)行加密,保存用戶信息到客戶端,然后發(fā)送在客戶端客戶端接收到這個(gè)JWT之后,保存在客戶端,之后帶著JWT訪問(wèn)其它模塊時(shí),資源服務(wù)器解析獲得用戶信息,進(jìn)行訪問(wèn),達(dá)到解放內(nèi)存的目的
            config 目錄下 TokenConfig類(lèi)

            package com.jt.auth.config;
            
            import org.springframework.context.annotation.Bean;
            import org.springframework.context.annotation.Configuration;
            import org.springframework.security.oauth3.provider.token.TokenStore;
            import org.springframework.security.oauth3.provider.token.store.JwtAccessTokenConverter;
            import org.springframework.security.oauth3.provider.token.store.JwtTokenStore;
            /*
            *創(chuàng)建jwt類(lèi)型令牌
            *構(gòu)建令牌的構(gòu)成有三部分:
            * header(頭部信息:令牌類(lèi)型)/
            * payload(數(shù)據(jù)信息-用戶信息,權(quán)限信息)/
            * SIGNATURE(簽名信息-對(duì)header和payload部分加密)
            * */
            @Configuration //配置對(duì)象--系統(tǒng)啟動(dòng)時(shí)底層會(huì)產(chǎn)生代理對(duì)象,來(lái)初始化一些對(duì)象
            public class TokenConfig {
                //定義令牌簽發(fā)口令(暗號(hào),規(guī)則),解密口令
                //當(dāng)客戶端在執(zhí)行登錄時(shí),加入有攜帶這個(gè)信息,認(rèn)證服務(wù)器可以簽發(fā)令牌
                
                private String SIGNING_KEY = "auth"; 在對(duì)header和payload簽名時(shí),需要的一個(gè)口令
                //構(gòu)建令牌生成器對(duì)象()
                @Bean
                public TokenStore tokenStore(){
                    return new JwtTokenStore(jwtAccessTokenConverter());//令牌生成器(jwt轉(zhuǎn)換器)
                }
               
                @Bean
                 //Jwt轉(zhuǎn)換器,將任何數(shù)據(jù)轉(zhuǎn)換為jwt字符串
                public JwtAccessTokenConverter jwtAccessTokenConverter(){
                    JwtAccessTokenConverter converter=new JwtAccessTokenConverter();
                    //設(shè)置加密/解密口令
                    converter.setSigningKey(SIGNING_KEY);
                    return converter;
                }
                
            }

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            創(chuàng)建認(rèn)證管理器對(duì)象

            在SecurityConfig中添加如下方法(后面授權(quán)服務(wù)器會(huì)用到):

            /**
                 * 配置認(rèn)證管理器(負(fù)責(zé)對(duì)客戶輸入的用戶信息進(jìn)行認(rèn)證),在其他配置類(lèi)中會(huì)用到這個(gè)對(duì)象
                 * @return
                 * @throws Exception
                 */
                @Bean
                public AuthenticationManager authenticationManagerBean()
                        throws Exception {
                    return super.authenticationManagerBean();
                }

            3.7 定義認(rèn)證授權(quán)核心配置

            授權(quán)服務(wù)器的核心配置
            package com.jt.auth.config;
            
            import lombok.AllArgsConstructor;
            import org.springframework.context.annotation.Bean;
            import org.springframework.context.annotation.Configuration;
            import org.springframework.http.HttpMethod;
            import org.springframework.security.authentication.AuthenticationManager;
            import org.springframework.security.core.userdetails.UserDetailsService;
            import org.springframework.security.crypto.password.PasswordEncoder;
            import org.springframework.security.oauth3.config.annotation.configurers.ClientDetailsServiceConfigurer;
            import org.springframework.security.oauth3.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
            import org.springframework.security.oauth3.config.annotation.web.configuration.EnableAuthorizationServer;
            import org.springframework.security.oauth3.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
            import org.springframework.security.oauth3.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
            import org.springframework.security.oauth3.provider.token.AuthorizationServerTokenServices;
            import org.springframework.security.oauth3.provider.token.DefaultTokenServices;
            import org.springframework.security.oauth3.provider.token.TokenEnhancerChain;
            import org.springframework.security.oauth3.provider.token.TokenStore;
            import org.springframework.security.oauth3.provider.token.store.JwtAccessTokenConverter;
            
            import java.util.Arrays;
            
            /**
             * 完成所有配置的組裝,在這個(gè)配置類(lèi)中完成認(rèn)證授權(quán),JWT令牌簽發(fā)等配置操作
             * 1)SpringSecurity (安全認(rèn)證和授權(quán))
             * 2)TokenConfig
             * 3)Oauth3(暫時(shí)不說(shuō))
             */
            
            @AllArgsConstructor
            @Configuration
            @EnableAuthorizationServer //開(kāi)啟認(rèn)證和授權(quán)服務(wù)
            public class Oauth3Config extends AuthorizationServerConfigurerAdapter {
                //此對(duì)象負(fù)責(zé)完成認(rèn)證管理
                private AuthenticationManager authenticationManager;
                //TokenStore負(fù)責(zé)完成令牌創(chuàng)建,信息讀取
                private TokenStore tokenStore;
                //負(fù)責(zé)獲取用戶詳情信息(username,password,client_id,grant_type,client_secret)
                //private ClientDetailsService clientDetailsService;
                //JWT令牌轉(zhuǎn)換器(基于用戶信息構(gòu)建令牌,解析令牌)
                private JwtAccessTokenConverter jwtAccessTokenConverter;
                //密碼加密匹配器對(duì)象
                private PasswordEncoder passwordEncoder;
                //負(fù)責(zé)獲取用戶信息信息
                private UserDetailsService userDetailsService;
            
                //設(shè)置認(rèn)證端點(diǎn)的配置(/oauth/token),客戶端通過(guò)這個(gè)路徑獲取JWT令牌
                @Override
                public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
                    endpoints
                    //配置認(rèn)證管理器
                    .authenticationManager(authenticationManager)
                    //驗(yàn)證用戶的方法獲得用戶詳情
                    .userDetailsService(userDetailsService)
                    //要求提交認(rèn)證使用post請(qǐng)求方式,提高安全性
                    .allowedTokenEndpointRequestMethods(HttpMethod.POST,HttpMethod.GET)
                    //要配置令牌的生成,由于令牌生成比較復(fù)雜,下面有方法實(shí)現(xiàn)
                    .tokenServices(tokenService());//這個(gè)不配置,默認(rèn)令牌為UUID.randomUUID().toString()
                }
            
                //定義令牌生成策略
                @Bean
                public AuthorizationServerTokenServices tokenService(){
                    //這個(gè)方法的目標(biāo)就是獲得一個(gè)令牌生成器
                    DefaultTokenServices services=new DefaultTokenServices();
                    //支持令牌刷新策略(令牌有過(guò)期時(shí)間)
                    services.setSupportRefreshToken(true);
                    //設(shè)置令牌生成策略(tokenStore在TokenConfig配置了,本次我們應(yīng)用JWT-定義了一種令牌格式)
                    services.setTokenStore(tokenStore);
                    //設(shè)置令牌增強(qiáng)(固定用法-令牌Payload部分允許添加擴(kuò)展數(shù)據(jù),例如用戶權(quán)限信息)
                    TokenEnhancerChain chain=new TokenEnhancerChain();
                    chain.setTokenEnhancers(
                            Arrays.asList(jwtAccessTokenConverter));
                    //令牌增強(qiáng)對(duì)象設(shè)置到令牌生成
                    services.setTokenEnhancer(chain);
                    //設(shè)置令牌有效期
                    services.setAccessTokenValiditySeconds(3600);//1小時(shí)
                    //刷新令牌應(yīng)用場(chǎng)景:一般在用戶登錄系統(tǒng)后,令牌快過(guò)期時(shí),系統(tǒng)自動(dòng)幫助用戶刷新令牌,提高用戶的體驗(yàn)感
                    services.setRefreshTokenValiditySeconds(3600*72);//3天
                    //配置客戶端詳情
                    //services.setClientDetailsService(clientDetailsService);
                    return services;
                }
            
                //設(shè)置客戶端詳情類(lèi)似于用戶詳情
                @Override
                public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
                    clients.inMemory()
                    //客戶端id
                    .withClient("gateway-client")
                    //客戶端秘鑰
                    .secret(passwordEncoder.encode("123456"))
                    //設(shè)置權(quán)限
                    .scopes("all")//all只是個(gè)名字而已和寫(xiě)abc效果相同
                    //允許客戶端進(jìn)行的操作  里面的字符串千萬(wàn)不能寫(xiě)錯(cuò)
                    .authorizedGrantTypes("password","refresh_token");
                }
                // 認(rèn)證成功后的安全約束配置
                @Override
                public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
                    //認(rèn)證通過(guò)后,允許客戶端進(jìn)行哪些操作
                    security
                    //公開(kāi)oauth/token_key端點(diǎn)
                    .tokenKeyAccess("permitAll()")
                    //公開(kāi)oauth/check_token端點(diǎn)
                    .checkTokenAccess("permitAll()")
                    //允許提交請(qǐng)求進(jìn)行認(rèn)證(申請(qǐng)令牌)
                    .allowFormAuthenticationForClients();
                }
            }
            Postman訪問(wèn)測(cè)試

            第一步:?jiǎn)?dòng)服務(wù)
            依次啟動(dòng)sca-auth服務(wù),sca-resource-gateway,sca-resource-ui服務(wù)。

            第二步:檢測(cè)sca-auth服務(wù)控制臺(tái)的Endpoints信息,例如:

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            打開(kāi)postman進(jìn)行登陸訪問(wèn)測(cè)試

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            {
                "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzAwNzYxMTAsInVzZXJfbmFtZSI6ImphY2siLCJhdXRob3JpdGllcyI6WyJzeXM6cmVzOmNyZWF0ZSIsInN5czpyZXM6cmV0cmlldmUiXSwianRpIjoiM2Q0MzExOTYtYmRkZi00Y2NhLWFmMDMtNWMzNGM4ZmJkNzQ3IiwiY2xpZW50X2lkIjoiZ2F0ZXdheS1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.GnrlqsZMSdagDaRQDZWDLbY7I7KUlXQgyXATcXXS6FI",
                "token_type": "bearer",

            4 資源服務(wù)器配置–sca-resource

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            4.1 構(gòu)建令牌配置對(duì)象

            package com.jt.resource.config;
            
            import org.springframework.context.annotation.Bean;
            import org.springframework.context.annotation.Configuration;
            import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
            import org.springframework.security.oauth3.config.annotation.web.configuration.EnableResourceServer;
            import org.springframework.security.oauth3.provider.token.TokenStore;
            import org.springframework.security.oauth3.provider.token.store.JwtAccessTokenConverter;
            import org.springframework.security.oauth3.provider.token.store.JwtTokenStore;
            
            /*
            *創(chuàng)建jwt類(lèi)型令牌
            *構(gòu)建令牌的構(gòu)成有三部分:
            * header(頭部信息:令牌類(lèi)型)/
            * payload(數(shù)據(jù)信息-用戶信息,權(quán)限信息)/
            * SIGNATURE(簽名信息-對(duì)header和payload部分加密)
            * */
            @Configuration //配置對(duì)象--系統(tǒng)啟動(dòng)時(shí)底層會(huì)產(chǎn)生代理對(duì)象,來(lái)初始化一些對(duì)象
            public class TokenConfig {
                //定義令牌簽發(fā)口令(暗號(hào),規(guī)則),解密口令
                //當(dāng)客戶端在執(zhí)行登錄時(shí),加入有攜帶這個(gè)信息,認(rèn)證服務(wù)器可以簽發(fā)令牌
                //在對(duì)header和payload簽名時(shí)
                private String SIGNING_KEY = "auth";
                //構(gòu)建令牌生成器對(duì)象()
                @Bean
                public TokenStore tokenStore(){
                    return new JwtTokenStore(jwtAccessTokenConverter());//令牌生成器(轉(zhuǎn)換器)
                }
                //Jwt轉(zhuǎn)換器,將任何數(shù)據(jù)轉(zhuǎn)換為jwt字符串
                @Bean
                public JwtAccessTokenConverter jwtAccessTokenConverter(){
                    JwtAccessTokenConverter converter=new JwtAccessTokenConverter();
                    //設(shè)置加密/解密口令
                    converter.setSigningKey(SIGNING_KEY);
                    return converter;
                }
            }

            4.2 資源服務(wù)令牌解析配置

            2.將對(duì)象轉(zhuǎn)換為json
                    //Gson-->toJson  (需要自己找依賴)
                    //fastjson-->JSON (spring-cloud-starter-alibaba-sentinel)
                    //jackson-->writeValueAsString (spring-boot-starter-web)
            package com.jt.resource.config;
            
            import com.alibaba.fastjson.JSON;
            import com.google.gson.Gson;
            import com.fasterxml.jackson.databind.ObjectMapper;
            import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.context.annotation.Bean;
            import org.springframework.context.annotation.Configuration;
            import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
            import org.springframework.security.config.annotation.web.builders.HttpSecurity;
            import org.springframework.security.oauth3.config.annotation.web.configuration.EnableResourceServer;
            import org.springframework.security.oauth3.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
            import org.springframework.security.oauth3.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
            import org.springframework.security.oauth3.provider.token.TokenStore;
            import org.springframework.security.web.AuthenticationEntryPoint;
            import org.springframework.security.web.access.AccessDeniedHandler;
            
            import javax.servlet.http.HttpServletResponse;
            import java.io.PrintWriter;
            import java.util.HashMap;
            import java.util.Map;
            
            /**
             * 資源服務(wù)器的配置,在這個(gè)對(duì)象
             * 1)JWT 令牌解析配置(客戶端帶著令牌訪問(wèn)資源時(shí),要對(duì)令牌進(jìn)行解析)
             * 2) 啟動(dòng)資源訪問(wèn)的授權(quán)配置(不是所有登陸用戶可以訪問(wèn)所有資源)
             */
            @Configuration
            @EnableResourceServer 此注解會(huì)啟動(dòng)資源服務(wù)器的默認(rèn)配置
            @EnableGlobalMethodSecurity(prePostEnabled = true) //執(zhí)行方法之前啟動(dòng)權(quán)限檢查
            public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
            
                @Autowired
                private TokenStore tokenStore;
                /**
                 * token服務(wù)配置
                 */
                @Override
                public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
                    //super.configure(resources);
                    //定義令牌生成策略,這里不是要?jiǎng)?chuàng)建令牌,是要解析令牌
                    resources.tokenStore(tokenStore);
                }
                /**
                 * 路由安全認(rèn)證配置
                 */
                @Override
                public void configure(HttpSecurity http) throws Exception {
                    //super.configure(http);
                    http.csrf().disable();//關(guān)閉跨域攻擊
                    //放行所有的資源訪問(wèn)(對(duì)資源的方問(wèn)不做認(rèn)證)
                    http.authorizeRequests().anyRequest().permitAll();
                    //http.authorizeRequests().mvcMatchers("/resource/**")
                    //        .authenticated(); //假如沒(méi)有認(rèn)證就訪問(wèn)會(huì)報(bào)401異常
                    //處理異常
                    http.exceptionHandling()
                            .accessDeniedHandler(accessDeniedHandler()); //403的異常處理  .拒絕訪問(wèn)的處理(AccessDeniedHandler類(lèi)型對(duì)象)
                           
                }
            
            
                @Bean
                public AccessDeniedHandler accessDeniedHandler() { //返回  AccessDeniedHandler對(duì)象
                    return (request,response, exception)->{
            
                        Map<String,Object> map = new HashMap<>();
                        map.put("state", HttpServletResponse.SC_FORBIDDEN);//403
                        map.put("message", "抱歉,沒(méi)有這個(gè)資源");
                        //1設(shè)置響應(yīng)數(shù)據(jù)的編碼
                        response.setCharacterEncoding("utf-8");
                        //2告訴瀏覽器響應(yīng)數(shù)據(jù)的內(nèi)容類(lèi)型以及編碼
                        response.setContentType("application/json;charset=utf-8");
                        //2.將對(duì)象轉(zhuǎn)換為json
            
                        //1.fastjson-->JSON (spring-cloud-starter-alibaba-sentinel)
                        //String jsonStr= JSON.toJSONString(map);//fastjson
                        //2.Gson-->toJson  (需要自己找依賴)
                        Gson gson = new Gson();
                        String jsonStr = gson.toJson(map);
                        //jackson-->writeValueAsString (spring-boot-starter-web)
                        //String jsonStr = new ObjectMapper().writeValueAsString(map);
                        PrintWriter writer = response.getWriter();
                        writer.println(jsonStr);
                        writer.flush();
                    };
                }
                
            }

            4.3 設(shè)置資源訪問(wèn)的權(quán)限

            在ResourceController的上傳方法上添加 @PreAuthorize(“hasAuthority(‘sys:res:create')”)注解,用于告訴底層框架方法此方法需要具備的權(quán)限,

              @PreAuthorize("hasAuthority('sys:res:create')")
              @PostMapping("/upload/")
               public String uploadFile(MultipartFile uploadFile) throws IOException {
                   ...
               }

            不加權(quán)限,會(huì)報(bào)403異常,并且展示我們修改403異常的信息返回在控制臺(tái)上

            4.4 啟動(dòng)服務(wù)訪問(wèn)測(cè)試

            4.4.1 訪問(wèn)auth認(rèn)證授權(quán)服務(wù)獲取token

            啟動(dòng)服務(wù)(sca-auth,sca-resource-gateway,sca-resource)
            執(zhí)行POSTMAN ,訪問(wèn) auth認(rèn)證授權(quán)服務(wù) http://localhost:9001/auth/oauth/token, 獲取token

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            4.4.2 攜帶TOKEN訪問(wèn)資源服務(wù)器

            復(fù)制access_token ,請(qǐng)求地址: http://localhost:9001/sca/resource/upload/
            1.設(shè)置請(qǐng)求頭(header),要攜帶令牌并指定請(qǐng)求的內(nèi)容類(lèi)型

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            2. 設(shè)置請(qǐng)求體(body),設(shè)置form-data,key要求為file類(lèi)型,參數(shù)名與你服務(wù)端controller文件上傳方法的參數(shù)名相同,值為你選擇的文件

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            4.4.3 對(duì)403異常前端頁(yè)面顯示
             function upload(file){
                    //定義一個(gè)表單
                    let form=new FormData();
                    //將圖片添加到表單中
                    form.append("uploadFile",file);
                    let url="http://localhost:9000/sca/resource/upload/";
                    //異步提交方式1
                    axios.post(url,form,{headers:{"Authorization":"Bearer "+localStorage.getItem("accessToken")}})
                         .then(function (response){
                             let result=response.data;
                             if(result.state==403){
                                 alert(result.message);
                                 return;
                             }
                             alert("upload ok");
                         })
                }

            1.啟動(dòng)服務(wù)(sca-auth,sca-resource-gateway,sca-resource)
            2.執(zhí)行登陸 localhost:8080/login-sso.html 獲取access_token令牌
            3.攜帶令牌訪問(wèn)資源(url中的前綴"sca"是在資源服務(wù)器中自己指定的,你的網(wǎng)關(guān)怎么配置的,你就怎么寫(xiě))

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            成功:

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            403異常,沒(méi)有訪問(wèn)權(quán)限

            利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)

            4.5 Oauth3規(guī)范

            oauth3定義了一種認(rèn)證授權(quán)協(xié)議,一種規(guī)范,此規(guī)范中定義了四種類(lèi)型的角色:
            1)資源有者(User)
            2)認(rèn)證授權(quán)服務(wù)器(jt-auth)
            3)資源服務(wù)器(jt-resource)
            4)客戶端應(yīng)用(jt-ui)
            同時(shí),在這種協(xié)議中規(guī)定了認(rèn)證授權(quán)時(shí)的幾種模式:
            1)密碼模式 (基于用戶名和密碼進(jìn)行認(rèn)證)
            2)授權(quán)碼模式(就是我們說(shuō)的三方認(rèn)證:QQ,微信,微博,。。。。)
            3)…

            4.6 面試問(wèn)題點(diǎn)

            單點(diǎn)登陸系統(tǒng)的設(shè)計(jì)架構(gòu)(微服務(wù)架構(gòu))
            服務(wù)的設(shè)計(jì)及劃分(資源服務(wù)器,認(rèn)證服務(wù)器,網(wǎng)關(guān)服務(wù)器,客戶端服務(wù))
            認(rèn)證及資源訪問(wèn)的流程(資源訪問(wèn)時(shí)要先認(rèn)證再訪問(wèn))
            認(rèn)證和授權(quán)時(shí)的一些關(guān)鍵技術(shù)(Spring Security,Jwt,Oauth3)
            FAQ 分析
            為什么要單點(diǎn)登陸(分布式系統(tǒng),再訪問(wèn)不同服務(wù)資源時(shí),不要總是要登陸,進(jìn)而改善用戶體驗(yàn))
            單點(diǎn)登陸解決方案?(市場(chǎng)常用兩種: spring security+jwt+oauth3,spring securit+redis+oauth3)
            Spring Security 是什么?(spring框架中的一個(gè)安全默認(rèn),實(shí)現(xiàn)了認(rèn)證和授權(quán)操作)
            JWT是什么?(一種令牌格式,一種令牌規(guī)范,通過(guò)對(duì)JSON數(shù)據(jù)采用一定的編碼,加密進(jìn)行令牌設(shè)計(jì))
            OAuth3是什么?(一種認(rèn)證和授權(quán)規(guī)范,定義了單點(diǎn)登陸中服務(wù)的劃分方式,認(rèn)證的相關(guān)類(lèi)型)

            5 Bug 分析

            401 : 訪問(wèn)資源時(shí)沒(méi)有認(rèn)證。
            403 : 訪問(wèn)資源時(shí)沒(méi)有權(quán)限。
            404:訪問(wèn)的資源找不到(一定要檢查你訪問(wèn)資源的url)
            405: 請(qǐng)求方式不匹配(客戶端請(qǐng)求方式是GET,服務(wù)端處理請(qǐng)求是Post就是這個(gè)問(wèn)題)
            500: 不看后臺(tái)無(wú)法解決?(error,warn)

            “利用Java spring實(shí)現(xiàn)單點(diǎn)登錄系統(tǒng)”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

            向AI問(wèn)一下細(xì)節(jié)

            免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

            AI