Java spring單點登錄系統

1.單點登錄系統介紹

多點登陸系統。應用起來相對繁瑣(每次訪問資源服務都需要重新登陸認證和授權)。與此同時,系統代碼的重復也比較高。所以單點登錄系統,倍受歡迎!
單點登錄系統,即多個站點共用一臺認證授權服務器,用戶在其中任何一個站點登錄後,可以免登錄訪問其他所有站點。而且,各站點間可以通過該登錄狀態直接交互。

在這裡插入圖片描述

2.簡單業務實現

在文件上傳的項目添加認證授權服務,義登錄頁面(login.html),然後在頁面中輸入自己的登陸賬號,登陸密碼,將請求提交給網關,然後網關將請求轉發到auth工程,登陸成功和失敗要返回json數據,按照如下結構實現

在這裡插入圖片描述

在02-sca工程創建 sca-auth子module,作為認證授權服務

在這裡插入圖片描述

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-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

2.2 項目配置文件

在sca-auth工程中創建bootstrap.yml文件

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

2.3添加項目啟動類

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 啟動並訪問項目

項目啟動時,系統會默認生成一個登陸密碼

在這裡插入圖片描述

打開瀏覽器輸入http://localhost:8071呈現登陸頁面

在這裡插入圖片描述

默認用戶名為user,密碼為系統啟動時,在控制臺呈現的密碼。執行登陸測試,登陸成功進入如下界面(因為沒有定義登陸頁面,所以會出現404)

3. 優化進一步設計

3.1 定義安全配置類 SecurityConfig

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

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//配置對象--系統啟動時底層會產生代理對象,來初始化一些對象
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//WebSecurityConfigurerAdapter 類是個適配器, 在配置的時候,需要我們自己寫個配置類去繼承他,然後編寫自己所特殊需要的配置
    //BCryptPasswordEncoder密碼加密對象比MD5安全性更高,MD5暴力反射可以破解過
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置認證管理器(負責對客戶輸入的用戶信息進行認證),在其他配置類中會用到這個對象
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManagerBean()
            throws Exception {
        return super.authenticationManagerBean();
    }

    /**在這個方法中定義登錄規則
     * 1)對所有請求放行(當前工程隻做認證)
     * 2)登錄成功信息的返回
     * 3)登錄失敗信息的返回
     * */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //禁用跨域
        http.csrf().disable();
        //放行所有請求
        http.authorizeRequests().anyRequest().permitAll();
        //登錄成功與失敗的處理
        http.formLogin()
                .successHandler(successHandler()) // .successHandler(AuthenticationSuccessHandler對象)
                .failureHandler(failureHandler());
    }

    @Bean
    //構建successHandler()方法來創建AuthenticationSuccessHandler對象
    public AuthenticationSuccessHandler successHandler(){
//        return new AuthenticationSuccessHandler() {
//            @Override
//            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//
//            }
//        }
        return (request,response,authentication) ->{
            //1.構建map對象,封裝響應數據
            Map<String,Object> map=new HashMap<>();
            map.put("state",200);
            map.put("message","login ok"); //登錄成功返回的響應信息
            //2.將map對象寫到客戶端
            writeJsonToClient(response,map);
        };
    }
    @Bean
    //failureHandler();方法來創建AuthenticationSuccessHandler對象
    public AuthenticationFailureHandler failureHandler(){
        return (request,response, e)-> {
            //1.構建map對象,封裝響應數據
            Map<String,Object> map=new HashMap<>();
            map.put("state",500);
            map.put("message","login failure");//登錄失敗返回的響應信息
            //2.將map對象寫到客戶端
            writeJsonToClient(response,map);
        };
    }
    //提取公共代碼,將對象轉為Json傳給客戶端, 構建writeJsonToClient();
    private void writeJsonToClient(HttpServletResponse response,
                                   Object object) throws IOException { // Object 類型,不隻是Map類型,說不準
        //2.將對象轉換為json
        //Gson-->toJson  (需要自己找依賴)
        //fastjson-->JSON (spring-cloud-starter-alibaba-sentinel)
        //jackson-->writeValueAsString (spring-boot-starter-web)
        String jsonStr=new ObjectMapper().writeValueAsString(object);
        //3.將json字符串寫到客戶端
        PrintWriter writer = response.getWriter();
        writer.println(jsonStr);
        writer.flush();
    }
}

3.2定義用戶信息處理對象

正常來說,用來與數據庫中的用戶信息作對比,認證是否正確,可否授權

在這裡插入圖片描述

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;

/**
 * 登錄時用戶信息的獲取和封裝會在此對象進行實現,
 * 在頁面上點擊登錄按鈕時,會調用這個對象的loadUserByUsername方法,
 * 頁面上輸入的用戶名會傳給這個方法的參數
 *
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {//獲取用戶詳細信息的接口
    @Autowired
    //BCryptPasswordEncoder密碼加密對象
    private BCryptPasswordEncoder passwordEncoder;
    //UserDetails用戶封裝用戶信息(認證和權限信息)
    @Override
    //重寫UserDetailsService 接口中的 loadUserByUsername();方法,定義用來核對數據庫數據和授於相應的權限
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        //1.基於用戶名查詢用戶信息(用戶名,用戶狀態,密碼,....)
        //Userinfo userinfo=userMapper.selectUserByUsername(username);數據庫用戶信息查詢操作簡寫瞭
        String encodedPassword=passwordEncoder.encode("123456");
        //2.查詢用戶權限信息(後面訪問數據庫)
        //這裡先給幾個假數據
        List<GrantedAuthority> authorities =
                AuthorityUtils.createAuthorityList(//這裡的權限信息先這麼寫,後面講
                        "sys:res:create", "sys:res:retrieve");
        //3.對用戶信息進行封裝
        return new User(username,encodedPassword,authorities);
    }
}

3.3 網關中登陸路由配置

在網關配置文件中添加如下配置

server:
  port: 9001
spring:
  application:
    name: sca-resource-gateway
  cloud:
    sentinel: #限流設計
      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表示負載均衡,底層默認使用ribbon實現
          predicates: #定義請求規則(請求需要按照此規則設計)
            - Path=/auth/login/** #請求路徑設計,單體架構
          filters:
            - StripPrefix=1 #轉發之前去掉path中第一層路徑

3.4基於Postman進行訪問測試

啟動sca-gateway,sca-auth服務,然後基於postman進行訪問測試

在這裡插入圖片描述

3.5 定義登陸頁面

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

<!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/[email protected]/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">
    <h3>Please Login</h3>
    <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/[email protected]/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",//定義監控點,vue底層會基於此監控點在內存中構建dom樹
        data:{ //此對象中定義頁面上要操作的數據
            username:"",
            password:""
        },
        methods: {//此位置定義所有業務事件處理函數
            doLogin() {
                //1.定義url
                let url = "http://localhost:9001/auth/oauth/token"
                //2.定義參數

                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.發送異步請求
                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 構建令牌配置對象

借助JWT(Json Web Token-是一種json格式)方式將用戶信息轉換為json格式,然後進行加密,保存用戶信息到客戶端,然後發送在客戶端客戶端接收到這個JWT之後,保存在客戶端,之後帶著JWT訪問其它模塊時,資源服務器解析獲得用戶信息,進行訪問,達到解放內存的目的
config 目錄下 TokenConfig類

package com.jt.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/*
*創建jwt類型令牌
*構建令牌的構成有三部分:
* header(頭部信息:令牌類型)/
* payload(數據信息-用戶信息,權限信息)/
* SIGNATURE(簽名信息-對header和payload部分加密)
* */
@Configuration //配置對象--系統啟動時底層會產生代理對象,來初始化一些對象
public class TokenConfig {
    //定義令牌簽發口令(暗號,規則),解密口令
    //當客戶端在執行登錄時,加入有攜帶這個信息,認證服務器可以簽發令牌
    
    private String SIGNING_KEY = "auth"; 在對header和payload簽名時,需要的一個口令
    //構建令牌生成器對象()
    @Bean
    public TokenStore tokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());//令牌生成器(jwt轉換器)
    }
   
    @Bean
     //Jwt轉換器,將任何數據轉換為jwt字符串
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter=new JwtAccessTokenConverter();
        //設置加密/解密口令
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }
    
}

在這裡插入圖片描述

創建認證管理器對象

在SecurityConfig中添加如下方法(後面授權服務器會用到):

    /**
     * 配置認證管理器(負責對客戶輸入的用戶信息進行認證),在其他配置類中會用到這個對象
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManagerBean()
            throws Exception {
        return super.authenticationManagerBean();
    }

3.7 定義認證授權核心配置

授權服務器的核心配置

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.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import java.util.Arrays;

/**
 * 完成所有配置的組裝,在這個配置類中完成認證授權,JWT令牌簽發等配置操作
 * 1)SpringSecurity (安全認證和授權)
 * 2)TokenConfig
 * 3)Oauth2(暫時不說)
 */

@AllArgsConstructor
@Configuration
@EnableAuthorizationServer //開啟認證和授權服務
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
    //此對象負責完成認證管理
    private AuthenticationManager authenticationManager;
    //TokenStore負責完成令牌創建,信息讀取
    private TokenStore tokenStore;
    //負責獲取用戶詳情信息(username,password,client_id,grant_type,client_secret)
    //private ClientDetailsService clientDetailsService;
    //JWT令牌轉換器(基於用戶信息構建令牌,解析令牌)
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    //密碼加密匹配器對象
    private PasswordEncoder passwordEncoder;
    //負責獲取用戶信息信息
    private UserDetailsService userDetailsService;

    //設置認證端點的配置(/oauth/token),客戶端通過這個路徑獲取JWT令牌
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
        //配置認證管理器
        .authenticationManager(authenticationManager)
        //驗證用戶的方法獲得用戶詳情
        .userDetailsService(userDetailsService)
        //要求提交認證使用post請求方式,提高安全性
        .allowedTokenEndpointRequestMethods(HttpMethod.POST,HttpMethod.GET)
        //要配置令牌的生成,由於令牌生成比較復雜,下面有方法實現
        .tokenServices(tokenService());//這個不配置,默認令牌為UUID.randomUUID().toString()
    }

    //定義令牌生成策略
    @Bean
    public AuthorizationServerTokenServices tokenService(){
        //這個方法的目標就是獲得一個令牌生成器
        DefaultTokenServices services=new DefaultTokenServices();
        //支持令牌刷新策略(令牌有過期時間)
        services.setSupportRefreshToken(true);
        //設置令牌生成策略(tokenStore在TokenConfig配置瞭,本次我們應用JWT-定義瞭一種令牌格式)
        services.setTokenStore(tokenStore);
        //設置令牌增強(固定用法-令牌Payload部分允許添加擴展數據,例如用戶權限信息)
        TokenEnhancerChain chain=new TokenEnhancerChain();
        chain.setTokenEnhancers(
                Arrays.asList(jwtAccessTokenConverter));
        //令牌增強對象設置到令牌生成
        services.setTokenEnhancer(chain);
        //設置令牌有效期
        services.setAccessTokenValiditySeconds(3600);//1小時
        //刷新令牌應用場景:一般在用戶登錄系統後,令牌快過期時,系統自動幫助用戶刷新令牌,提高用戶的體驗感
        services.setRefreshTokenValiditySeconds(3600*72);//3天
        //配置客戶端詳情
        //services.setClientDetailsService(clientDetailsService);
        return services;
    }

    //設置客戶端詳情類似於用戶詳情
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
        //客戶端id
        .withClient("gateway-client")
        //客戶端秘鑰
        .secret(passwordEncoder.encode("123456"))
        //設置權限
        .scopes("all")//all隻是個名字而已和寫abc效果相同
        //允許客戶端進行的操作  裡面的字符串千萬不能寫錯
        .authorizedGrantTypes("password","refresh_token");
    }
    // 認證成功後的安全約束配置
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //認證通過後,允許客戶端進行哪些操作
        security
        //公開oauth/token_key端點
        .tokenKeyAccess("permitAll()")
        //公開oauth/check_token端點
        .checkTokenAccess("permitAll()")
        //允許提交請求進行認證(申請令牌)
        .allowFormAuthenticationForClients();
    }
}

Postman訪問測試

第一步:啟動服務
依次啟動sca-auth服務,sca-resource-gateway,sca-resource-ui服務。

第二步:檢測sca-auth服務控制臺的Endpoints信息,例如:

在這裡插入圖片描述

打開postman進行登陸訪問測試

在這裡插入圖片描述

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

4 資源服務器配置–sca-resource

在這裡插入圖片描述

4.1 構建令牌配置對象

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.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/*
*創建jwt類型令牌
*構建令牌的構成有三部分:
* header(頭部信息:令牌類型)/
* payload(數據信息-用戶信息,權限信息)/
* SIGNATURE(簽名信息-對header和payload部分加密)
* */
@Configuration //配置對象--系統啟動時底層會產生代理對象,來初始化一些對象
public class TokenConfig {
    //定義令牌簽發口令(暗號,規則),解密口令
    //當客戶端在執行登錄時,加入有攜帶這個信息,認證服務器可以簽發令牌
    //在對header和payload簽名時
    private String SIGNING_KEY = "auth";
    //構建令牌生成器對象()
    @Bean
    public TokenStore tokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());//令牌生成器(轉換器)
    }
    //Jwt轉換器,將任何數據轉換為jwt字符串
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter=new JwtAccessTokenConverter();
        //設置加密/解密口令
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }
}

4.2 資源服務令牌解析配置

2.將對象轉換為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.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.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;

/**
 * 資源服務器的配置,在這個對象
 * 1)JWT 令牌解析配置(客戶端帶著令牌訪問資源時,要對令牌進行解析)
 * 2) 啟動資源訪問的授權配置(不是所有登陸用戶可以訪問所有資源)
 */
@Configuration
@EnableResourceServer 此註解會啟動資源服務器的默認配置
@EnableGlobalMethodSecurity(prePostEnabled = true) //執行方法之前啟動權限檢查
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;
    /**
     * token服務配置
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        //super.configure(resources);
        //定義令牌生成策略,這裡不是要創建令牌,是要解析令牌
        resources.tokenStore(tokenStore);
    }
    /**
     * 路由安全認證配置
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //super.configure(http);
        http.csrf().disable();//關閉跨域攻擊
        //放行所有的資源訪問(對資源的方問不做認證)
        http.authorizeRequests().anyRequest().permitAll();
        //http.authorizeRequests().mvcMatchers("/resource/**")
        //        .authenticated(); //假如沒有認證就訪問會報401異常
        //處理異常
        http.exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler()); //403的異常處理  .拒絕訪問的處理(AccessDeniedHandler類型對象)
               
    }


    @Bean
    public AccessDeniedHandler accessDeniedHandler() { //返回  AccessDeniedHandler對象
        return (request,response, exception)->{

            Map<String,Object> map = new HashMap<>();
            map.put("state", HttpServletResponse.SC_FORBIDDEN);//403
            map.put("message", "抱歉,沒有這個資源");
            //1設置響應數據的編碼
            response.setCharacterEncoding("utf-8");
            //2告訴瀏覽器響應數據的內容類型以及編碼
            response.setContentType("application/json;charset=utf-8");
            //2.將對象轉換為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 設置資源訪問的權限

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

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

不加權限,會報403異常,並且展示我們修改403異常的信息返回在控制臺上

4.4 啟動服務訪問測試

4.4.1 訪問auth認證授權服務獲取token

啟動服務(sca-auth,sca-resource-gateway,sca-resource)
執行POSTMAN ,訪問 auth認證授權服務 http://localhost:9001/auth/oauth/token, 獲取token

在這裡插入圖片描述

4.4.2 攜帶TOKEN訪問資源服務器

復制access_token ,請求地址: http://localhost:9001/sca/resource/upload/
1.設置請求頭(header),要攜帶令牌並指定請求的內容類型

在這裡插入圖片描述

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

在這裡插入圖片描述

4.4.3 對403異常前端頁面顯示

 function upload(file){
        //定義一個表單
        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.啟動服務(sca-auth,sca-resource-gateway,sca-resource)
2.執行登陸 localhost:8080/login-sso.html 獲取access_token令牌
3.攜帶令牌訪問資源(url中的前綴”sca”是在資源服務器中自己指定的,你的網關怎麼配置的,你就怎麼寫)

在這裡插入圖片描述

成功:

在這裡插入圖片描述

403異常,沒有訪問權限

在這裡插入圖片描述

4.5 Oauth2規范

oauth2定義瞭一種認證授權協議,一種規范,此規范中定義瞭四種類型的角色:
1)資源有者(User)
2)認證授權服務器(jt-auth)
3)資源服務器(jt-resource)
4)客戶端應用(jt-ui)
同時,在這種協議中規定瞭認證授權時的幾種模式:
1)密碼模式 (基於用戶名和密碼進行認證)
2)授權碼模式(就是我們說的三方認證:QQ,微信,微博,。。。。)
3)…

4.6 面試問題點

單點登陸系統的設計架構(微服務架構)
服務的設計及劃分(資源服務器,認證服務器,網關服務器,客戶端服務)
認證及資源訪問的流程(資源訪問時要先認證再訪問)
認證和授權時的一些關鍵技術(Spring Security,Jwt,Oauth2)
FAQ 分析
為什麼要單點登陸(分佈式系統,再訪問不同服務資源時,不要總是要登陸,進而改善用戶體驗)
單點登陸解決方案?(市場常用兩種: spring security+jwt+oauth2,spring securit+redis+oauth2)
Spring Security 是什麼?(spring框架中的一個安全默認,實現瞭認證和授權操作)
JWT是什麼?(一種令牌格式,一種令牌規范,通過對JSON數據采用一定的編碼,加密進行令牌設計)
OAuth2是什麼?(一種認證和授權規范,定義瞭單點登陸中服務的劃分方式,認證的相關類型)

5 Bug 分析

401 : 訪問資源時沒有認證。
403 : 訪問資源時沒有權限。
404:訪問的資源找不到(一定要檢查你訪問資源的url)
405: 請求方式不匹配(客戶端請求方式是GET,服務端處理請求是Post就是這個問題)
500: 不看後臺無法解決?(error,warn)

到此這篇關於Java spring單點登錄系統的文章就介紹到這瞭,更多相關Java單點登錄內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: