1.單點(diǎn)登錄系統(tǒng)介紹
多點(diǎn)登陸系統(tǒng)。應(yīng)用起來(lái)相對(duì)繁瑣(每次訪(fǎng)問(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ù)器,用戶(hù)在其中任何一個(gè)站點(diǎn)登錄后,可以免登錄訪(fǎng)問(wèn)其他所有站點(diǎn)。而且,各站點(diǎn)間可以通過(guò)該登錄狀態(tài)直接交互。
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)
在02-sca工程創(chuàng)建 sca-auth子module,作為認(rèn)證授權(quán)服務(wù)
2.1添加依賴(lài)
<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 項(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)并訪(fǎng)問(wèn)項(xiàng)目
項(xiàng)目啟動(dòng)時(shí),系統(tǒng)會(huì)默認(rèn)生成一個(gè)登陸密碼
打開(kāi)瀏覽器輸入http://localhost:8071呈現(xiàn)登陸頁(yè)面
默認(rèn)用戶(hù)名為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ì)客戶(hù)輸入的用戶(hù)信息進(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ě)到客戶(hù)端 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ě)到客戶(hù)端 writeJsonToClient(response,map); }; } //提取公共代碼,將對(duì)象轉(zhuǎn)為Json傳給客戶(hù)端, 構(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 (需要自己找依賴(lài)) //fastjson-->JSON (spring-cloud-starter-alibaba-sentinel) //jackson-->writeValueAsString (spring-boot-starter-web) String jsonStr=new ObjectMapper().writeValueAsString(object); //3.將json字符串寫(xiě)到客戶(hù)端 PrintWriter writer = response.getWriter(); writer.println(jsonStr); writer.flush(); } }
3.2定義用戶(hù)信息處理對(duì)象
正常來(lái)說(shuō),用來(lái)與數(shù)據(jù)庫(kù)中的用戶(hù)信息作對(duì)比,認(rèn)證是否正確,可否授權(quán)
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í)用戶(hù)信息的獲取和封裝會(huì)在此對(duì)象進(jìn)行實(shí)現(xiàn), * 在頁(yè)面上點(diǎn)擊登錄按鈕時(shí),會(huì)調(diào)用這個(gè)對(duì)象的loadUserByUsername方法, * 頁(yè)面上輸入的用戶(hù)名會(huì)傳給這個(gè)方法的參數(shù) * */ @Service public class UserDetailsServiceImpl implements UserDetailsService {//獲取用戶(hù)詳細(xì)信息的接口 @Autowired //BCryptPasswordEncoder密碼加密對(duì)象 private BCryptPasswordEncoder passwordEncoder; //UserDetails用戶(hù)封裝用戶(hù)信息(認(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.基于用戶(hù)名查詢(xún)用戶(hù)信息(用戶(hù)名,用戶(hù)狀態(tài),密碼,....) //Userinfo userinfo=userMapper.selectUserByUsername(username);數(shù)據(jù)庫(kù)用戶(hù)信息查詢(xún)操作簡(jiǎn)寫(xiě)了 String encodedPassword=passwordEncoder.encode("123456"); //2.查詢(xún)用戶(hù)權(quán)限信息(后面訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)) //這里先給幾個(gè)假數(shù)據(jù) List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(//這里的權(quán)限信息先這么寫(xiě),后面講 "sys:res:create", "sys:res:retrieve"); //3.對(duì)用戶(hù)信息進(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)行訪(fǎng)問(wèn)測(cè)試
啟動(dòng)sca-gateway,sca-auth服務(wù),然后基于postman進(jìn)行訪(fǎng)問(wèn)測(cè)試
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"> <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/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格式)方式將用戶(hù)信息轉(zhuǎn)換為json格式,然后進(jìn)行加密,保存用戶(hù)信息到客戶(hù)端,然后發(fā)送在客戶(hù)端客戶(hù)端接收到這個(gè)JWT之后,保存在客戶(hù)端,之后帶著JWT訪(fǎng)問(wèn)其它模塊時(shí),資源服務(wù)器解析獲得用戶(hù)信息,進(jìn)行訪(fǎng)問(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.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; /* *創(chuàng)建jwt類(lèi)型令牌 *構(gòu)建令牌的構(gòu)成有三部分: * header(頭部信息:令牌類(lèi)型)/ * payload(數(shù)據(jù)信息-用戶(hù)信息,權(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)客戶(hù)端在執(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; } }
創(chuàng)建認(rèn)證管理器對(duì)象
在SecurityConfig中添加如下方法(后面授權(quán)服務(wù)器會(huì)用到):
/** * 配置認(rèn)證管理器(負(fù)責(zé)對(duì)客戶(hù)輸入的用戶(hù)信息進(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.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; /** * 完成所有配置的組裝,在這個(gè)配置類(lèi)中完成認(rèn)證授權(quán),JWT令牌簽發(fā)等配置操作 * 1)SpringSecurity (安全認(rèn)證和授權(quán)) * 2)TokenConfig * 3)Oauth2(暫時(shí)不說(shuō)) */ @AllArgsConstructor @Configuration @EnableAuthorizationServer //開(kāi)啟認(rèn)證和授權(quán)服務(wù) public class Oauth2Config extends AuthorizationServerConfigurerAdapter { //此對(duì)象負(fù)責(zé)完成認(rèn)證管理 private AuthenticationManager authenticationManager; //TokenStore負(fù)責(zé)完成令牌創(chuàng)建,信息讀取 private TokenStore tokenStore; //負(fù)責(zé)獲取用戶(hù)詳情信息(username,password,client_id,grant_type,client_secret) //private ClientDetailsService clientDetailsService; //JWT令牌轉(zhuǎn)換器(基于用戶(hù)信息構(gòu)建令牌,解析令牌) private JwtAccessTokenConverter jwtAccessTokenConverter; //密碼加密匹配器對(duì)象 private PasswordEncoder passwordEncoder; //負(fù)責(zé)獲取用戶(hù)信息信息 private UserDetailsService userDetailsService; //設(shè)置認(rèn)證端點(diǎn)的配置(/oauth/token),客戶(hù)端通過(guò)這個(gè)路徑獲取JWT令牌 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints //配置認(rèn)證管理器 .authenticationManager(authenticationManager) //驗(yàn)證用戶(hù)的方法獲得用戶(hù)詳情 .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ù),例如用戶(hù)權(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)景:一般在用戶(hù)登錄系統(tǒng)后,令牌快過(guò)期時(shí),系統(tǒng)自動(dòng)幫助用戶(hù)刷新令牌,提高用戶(hù)的體驗(yàn)感 services.setRefreshTokenValiditySeconds(3600*72);//3天 //配置客戶(hù)端詳情 //services.setClientDetailsService(clientDetailsService); return services; } //設(shè)置客戶(hù)端詳情類(lèi)似于用戶(hù)詳情 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() //客戶(hù)端id .withClient("gateway-client") //客戶(hù)端秘鑰 .secret(passwordEncoder.encode("123456")) //設(shè)置權(quán)限 .scopes("all")//all只是個(gè)名字而已和寫(xiě)abc效果相同 //允許客戶(hù)端進(jìn)行的操作 里面的字符串千萬(wàn)不能寫(xiě)錯(cuò) .authorizedGrantTypes("password","refresh_token"); } // 認(rèn)證成功后的安全約束配置 @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { //認(rèn)證通過(guò)后,允許客戶(hù)端進(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訪(fǎng)問(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信息,例如:
打開(kāi)postman進(jìn)行登陸訪(fǎng)問(wèn)測(cè)試
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzAwNzYxMTAsInVzZXJfbmFtZSI6ImphY2siLCJhdXRob3JpdGllcyI6WyJzeXM6cmVzOmNyZWF0ZSIsInN5czpyZXM6cmV0cmlldmUiXSwianRpIjoiM2Q0MzExOTYtYmRkZi00Y2NhLWFmMDMtNWMzNGM4ZmJkNzQ3IiwiY2xpZW50X2lkIjoiZ2F0ZXdheS1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.GnrlqsZMSdagDaRQDZWDLbY7I7KUlXQgyXATcXXS6FI", "token_type": "bearer",
4 資源服務(wù)器配置Csca-resource
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.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; /* *創(chuàng)建jwt類(lèi)型令牌 *構(gòu)建令牌的構(gòu)成有三部分: * header(頭部信息:令牌類(lèi)型)/ * payload(數(shù)據(jù)信息-用戶(hù)信息,權(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)客戶(hù)端在執(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 (需要自己找依賴(lài)) //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; /** * 資源服務(wù)器的配置,在這個(gè)對(duì)象 * 1)JWT 令牌解析配置(客戶(hù)端帶著令牌訪(fǎng)問(wèn)資源時(shí),要對(duì)令牌進(jìn)行解析) * 2) 啟動(dòng)資源訪(fǎng)問(wèn)的授權(quán)配置(不是所有登陸用戶(hù)可以訪(fǎng)問(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)閉跨域攻擊 //放行所有的資源訪(fǎng)問(wèn)(對(duì)資源的方問(wèn)不做認(rèn)證) http.authorizeRequests().anyRequest().permitAll(); //http.authorizeRequests().mvcMatchers("/resource/**") // .authenticated(); //假如沒(méi)有認(rèn)證就訪(fǎng)問(wèn)會(huì)報(bào)401異常 //處理異常 http.exceptionHandling() .accessDeniedHandler(accessDeniedHandler()); //403的異常處理 .拒絕訪(fǎng)問(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 (需要自己找依賴(lài)) 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è)置資源訪(fǎng)問(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ù)訪(fǎng)問(wèn)測(cè)試
4.4.1 訪(fǎng)問(wèn)auth認(rèn)證授權(quán)服務(wù)獲取token
啟動(dòng)服務(wù)(sca-auth,sca-resource-gateway,sca-resource)
執(zhí)行POSTMAN ,訪(fǎng)問(wèn) auth認(rèn)證授權(quán)服務(wù) http://localhost:9001/auth/oauth/token, 獲取token
4.4.2 攜帶TOKEN訪(fǎng)問(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)型
2. 設(shè)置請(qǐng)求體(body),設(shè)置form-data,key要求為file類(lèi)型,參數(shù)名與你服務(wù)端controller文件上傳方法的參數(shù)名相同,值為你選擇的文件
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.攜帶令牌訪(fǎng)問(wèn)資源(url中的前綴"sca"是在資源服務(wù)器中自己指定的,你的網(wǎng)關(guān)怎么配置的,你就怎么寫(xiě))
成功:
403異常,沒(méi)有訪(fǎng)問(wèn)權(quán)限
4.5 Oauth2規(guī)范
oauth2定義了一種認(rèn)證授權(quán)協(xié)議,一種規(guī)范,此規(guī)范中定義了四種類(lèi)型的角色:
1)資源有者(User)
2)認(rèn)證授權(quán)服務(wù)器(jt-auth)
3)資源服務(wù)器(jt-resource)
4)客戶(hù)端應(yīng)用(jt-ui)
同時(shí),在這種協(xié)議中規(guī)定了認(rèn)證授權(quán)時(shí)的幾種模式:
1)密碼模式 (基于用戶(hù)名和密碼進(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ù)器,客戶(hù)端服務(wù))
認(rèn)證及資源訪(fǎng)問(wèn)的流程(資源訪(fǎng)問(wèn)時(shí)要先認(rèn)證再訪(fǎng)問(wèn))
認(rèn)證和授權(quán)時(shí)的一些關(guān)鍵技術(shù)(Spring Security,Jwt,Oauth2)
FAQ 分析
為什么要單點(diǎn)登陸(分布式系統(tǒng),再訪(fǎng)問(wèn)不同服務(wù)資源時(shí),不要總是要登陸,進(jìn)而改善用戶(hù)體驗(yàn))
單點(diǎn)登陸解決方案?(市場(chǎng)常用兩種: spring security+jwt+oauth2,spring securit+redis+oauth2)
Spring Security 是什么?(spring框架中的一個(gè)安全默認(rèn),實(shí)現(xiàn)了認(rèn)證和授權(quán)操作)
JWT是什么?(一種令牌格式,一種令牌規(guī)范,通過(guò)對(duì)JSON數(shù)據(jù)采用一定的編碼,加密進(jìn)行令牌設(shè)計(jì))
OAuth2是什么?(一種認(rèn)證和授權(quán)規(guī)范,定義了單點(diǎn)登陸中服務(wù)的劃分方式,認(rèn)證的相關(guān)類(lèi)型)
…
5 Bug 分析
401 : 訪(fǎng)問(wèn)資源時(shí)沒(méi)有認(rèn)證。
403 : 訪(fǎng)問(wèn)資源時(shí)沒(méi)有權(quán)限。
404:訪(fǎng)問(wèn)的資源找不到(一定要檢查你訪(fǎng)問(wèn)資源的url)
405: 請(qǐng)求方式不匹配(客戶(hù)端請(qǐng)求方式是GET,服務(wù)端處理請(qǐng)求是Post就是這個(gè)問(wèn)題)
500: 不看后臺(tái)無(wú)法解決?(error,warn)
…
到此這篇關(guān)于Java spring單點(diǎn)登錄系統(tǒng)的文章就介紹到這了,更多相關(guān)Java單點(diǎn)登錄內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://blog.csdn.net/weixin_58678891/article/details/119959552