踏上嵌入式之路,一去不復返!_網頁設計公司

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

大家好,昨天又有一批新的小夥伴加入,這裏重新簡單介紹一下自己的情況。和很多應屆生一樣,剛畢業有着找工作的迷茫和擔憂(這裏不包含已經工作的前輩),這是我去年也有過這樣的經歷;今天有一個網友加我微信,說在b站上看了我的介紹(b站ID:TXP嵌入式),學習經歷很像他,說到這裏,我也不由噓噓的感慨以前的時光,確實我大學裏面不是本專業的,是學汽車的,在學校里的時光,和朋友搞過一年多的汽車保養和維修的創業(其實也不算創業,當時想着提高自己的技術,這算是第一次體驗過如何去經營一個組織的經驗,確實當老闆沒有想象的那麼簡單,要有各方面的抗壓技能等等!),下面我就簡單說一下,我是如何從零基礎到linux開發崗位的一個經驗介紹:

 

正如上面所說,我是跨專業來學电子的,在幹了一年左右的汽車保養和維修,我果斷的放棄了汽車行業(其實最為直接的原因,就是出來上班工資太低,而且以我的體質不適合干汽車維修,為啥這樣說:舉個簡單,在進學院的實驗室管理員,這個過程中要經過大概三個月的魔鬼訓練,每天跑步大概6公里左右,然後跑完做各種難度的動作,到後面舉汽車的輪胎也是有的,說實話,這個我真舉不起,有些輪胎太大了,實在沒有力氣舉(我本身人又瘦小,所以你懂的當時是有多麼難的),後面我挺過來了,進了實驗室(這裏我很羡慕學电子或者計算機的網友能夠進入本學院的實驗室,順便再插一句,我學歷沒有很多大佬那麼高,我只是一名非常普通的大專生),這個過程我不展開詳細講了,今天的核心內容是如何從零基礎到linux開發崗位。

 

我是大二下學期快要結束的時候開始正式學編程(其實大二也有學,但是中途參加過兩次國家行業大賽:汽車新能源服務大賽和智能交通大賽,其中一次獲得三等獎(這裏非常感謝當時學院和老師的指導以及搭檔老王,這兩次比賽經歷算是我大學裏面最難忘的記憶了)。其實我學編程說實話,我的大學班主任算是我的啟蒙老師(這裏真的非常感謝,不然現在我自己也不知道做啥行業,沒有方向感),他給我推薦了一本c語言書籍和野火的開發板來學習,後面我就一發不可收拾了,一直自學堅持到現在,這其中走的彎路太多,比如:在我自己建的一個交流群裏面,經常有網友問,到底學哪個好,其實當初我也是這樣,在入門嵌入式過程中,會發現真的太多東西要學,苦於當時身邊沒有人指導一下方向(我的班主任他是研究生畢業,一出來就來到我們學校當老師了,當時也有找老師談過,但是自己還是沒有找到方向),所以學的過程中非常累,抓不住重點,看到那樣好,就馬上跟風去學,其實自己內心沒有去真正思考過這個技術到底是不是自己喜歡和以後吃飯的傢伙(這是站在我今天已經工作的角度簡單分析的。),因為嵌入式太廣了,甚至很多人連嵌入式到底是啥也不知道(所謂“嵌入式”,名如其文,指在很多芯片中,其所包含的處理器就像嵌入在裏面不為人知一樣,嵌入式領域的應用非常之廣泛,是處理器除了服務器和PC領域之外的主要應用領域:實時(Real Time)嵌入式領域(也就是我們常說的RTOS)、移動(Mobile)領域(比如要搞複雜的系統:linux、安卓等)、傳統嵌入式領域(一般就是單片機裸機了)),其實這段話,我是在網絡上看到一個前輩總結出的,結合自身體驗,確實是分為這三大領域。

 

回到剛才學嵌入式過程的哪個話題,其實在過程中最大的難處在於:缺乏鋼鐵一般的毅力(如果真喜歡嵌入式這個行業的話,一定要堅持下去,會有意想不到的結果)、良好的學術氛圍(我指的是自學的人沒有好學習的環境和交流,全靠一個人走“夜路”,走到哪裡算哪裡,也不知道到底是對還是錯)、缺乏項目經驗;第三點是很多人最為缺乏的(包括我自己也是這樣,其實有的網友說,學校做的畢設和企業裏面做的項目差別太大了,要考慮到很多方面,這個說的確實是這樣,但是我要說的是,先把學校畢設和一些小項目,認真做好,技術經驗和解決問題的能力就是在一個一個的小項目中成長的,帶領你如何走進嵌入式開發的大門,所以在學校裏面的實戰,一定要認真做好,有機會和對嵌入式感興趣的,可以去實驗室,不是說非要進入實驗室,只是說實驗室的環境稍微可能要好一點;把學校裏面的項目做好,後面有機會可以擴展到企業項目,只要你有能力,機會總會來的,就怕你沒有實力和準備,不然就算有機會來的,你也只能幹瞪眼和干著急,沒啥用啊,因為你不會啊,這就是現實;以後出來上班也是這樣,你憑什麼拿高薪,只有有能力給公司產生利潤,老闆就願意給你高薪,因為你有能力,值得這個價。)這裏可能又扯外了,哈哈,不過現實就是如此,只有自己有能力,資源和一切等,你都有機會得到。

 

這學習嵌入式過程,我自己走的彎路確實走的太多,而且很多東西又沒有學會,比如說一開始學過51單片機,後面看到別人玩pic、avr單片機,我也跟着去玩,最後發現自己還是沒有學會pic和avr,就是因為自己學的雜亂無比,其實這裏我建議在學的過程中,最好去網絡上找小項目練手,這樣會學的比較好,不要只看視頻教程學,這樣是學不到啥東西的,只是說帶你走了一個過程,熟悉了一下這種類型的開發以及一些協議和外設等等,實際你自己真要動手去做的話,你會發現不知道從哪裡下手,所以說要多練,養成一個良好的編程習慣以及當拿到一個小項目的時候,我該怎麼去入手,做好大體規劃,然後再去深入細節(這裏模塊化思維很重要,真的);然後後面,發現python和opencv好火,又去跟風學這個,又走了彎路,又沒有學會,這裏你可以發現我,不知道自己真正要學什麼,以後往哪裡走,只是一味的跟風(說的難聽一點,就是在裝逼);後面臨近畢業我真正開始意識到這一點,我得先深入一個方向去學,然後再去擴展,於是乎,我最開始是從pcb去深入的,我報了一個培訓班,認真學了三個月,確實出去能夠幹活了,這裏非常感謝凡億的鄭老師很助教的辛苦付出。在我學完pcb就直接去了一個非常小的公司裏面實習,軟件和硬件都要做,那時候我軟件很差勁,就畫板好一點,畫兩層板和四層還行,六層的不行,沒有實戰過,學的時候,跟實戰還是有差別的,而且小公司裏面不做那麼高層的板子,六層板的話,成本還是蠻高的,更別說8層、12層那種板子了。

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

 

那我是如何接觸到linux的呢?沒錯,我在那家小公司實習了兩個月,我就走了,為啥,因為我認識到了自己到底要啥了,這個時候我無意之間買了朱有鵬老師的嵌入式linux核心課程,對於我這種小白來說,這課程可以改變了我職業發展,這是我真心話,因為我出來上班,直接跳過做單片機,直接上手linux,其實這裏我也是比較幸運的,自己也比較大膽,在今天特殊情況下,一來深圳就辭職了上家公司(我正式上班的第一家公司是做手機屏幕,不算是驅動,只是簡單調試显示和觸摸,也不是我喜歡的;當然這裏我是有準備跳槽的。)在上家公司我又待了兩個月左右,這兩個月,我也不知道自己是怎麼走過來的,我拼了命在學朱老師課程,從最基本的linux命令(之前在學校的時候我接觸過,但是太零散了。)、以及基本的c語言基礎、c語言提高、linux應用編程,到現在的linux內核和驅動,在那兩個月里,我每天下班都會去看課程去學(晚上下班7:30.有時候晚一點,早上上班9點,然後周末是單休),每天晚上我都學到凌晨兩點左右,這和我學習pcb的時候,一樣,這種感覺現在回憶起來,讓人噓噓。期間我也開始寫公眾號了(把自己學的知識點,總結出來,並分享出去,讀者也可以發現,我寫的文章並不好,沒有深度,但是這是一個人成長的過程),就是我現在這個公眾號,這期間,我改變了自己的學習方式,養成了喜歡和別人交流技術,也認識了很多前輩和網友,這裏他們的幫助對我影響也非常大(過程中得到了很多指導以及一些網友提的不足之處,讓我糾正了很多誤區。)。

 

其實我去找linux崗位的時候,內心也是非常害怕,怕找不到工作的,因為我linux應用,只是學完了課程內容知識,並沒去找項目練手,只是想快點上手linux;於是乎,我在過年期間就開始頭簡歷,這過程確實難了,和現在有網友找工作的感受一樣;我投了很多簡歷,但是只收到的回復不是很多,因為缺乏項目經驗以及學歷的原因,沒有達到要求,但是我明白,這個過程是自己成長的過程,並沒有放棄對linux崗位的追求,終於後面經過在假期的磨鍊,我又投了幾家,終於收到了offer(這其中我自己又去認真的把課程中的一個網絡編程小項目又熟悉了一邊,以及c語言的基礎,即使是到現在我依然會去買一些書籍,繼續去鞏固基礎):

 

 

後面通過了面試,就順利拿到了linux崗位開發了。這裏我可能我沒有說的很具體,但是這其中的艱辛只有我自己知道,多少次想過放棄從事這個行業,但是又一次次我跌倒了,又重新站起來了,因為我奶奶經常告誡我:年輕的時候要多吃點苦,先苦后甜的道理,我聽過我奶奶講她以前的故事,那時候的苦,我的苦比起她吃的苦,簡直不能比)。

 

好了,今天就分享到這裏了,這就是我的簡單故事,平常和普通,只有靠自己的努力和行動才能得到你想要的。後面我會繼續分享技術文章;如果有網友想進交流群的話,可以加我微信,回復技術,我會拉您進群,之所以這樣,是為了防止有些人,進了交流群裏面,老是發一些與技術無關的鏈接和廣告,這樣的話,就失去了交流群的意義了。最後,說一句:感謝各位網友的支持。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

面試問題—JAVA程序CPU佔用過高怎麼定位_網頁設計

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

今天一個電話面試問了這個問題。回來查了下答案,自己也順帶操作一遍,做個記錄。之前只知道jstack工具可以查看線程狀態這些。比如死鎖這些,主要是之前不知道top -H -p pid這個命令的使用,這命令可以看到進程下麵線程信息,拿到線程ID,然後再結合jstack命令使用就可以解決這個問題了。下面記錄一下具體的操作步驟:

1.打個jar包丟到機器上運行

package com.nijunyang.test;



public class TestApplication {

    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            new Thread(()->test()).start();
        }

    }

    public static void test() {
        while (true) {
            int a = 1  + 6;
            System.out.println(a);
        }
    }
}

使用這個maven插件 打包jar

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.nijunyang.test.TestApplication</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

2. java -jar test-0.0.1-SNAPSHOT-jar-with-dependencies.jar  運行程序

 

 一直在輸出

3.top |grep java  或者 jps指令找到java進程的pid(6167)

 

 

4. top -H -p pid   以線程的形式查看該進程 top -H -p 6167

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

 

 因為我們程序是起了50個線程 所以這裏就會展示這個進程中的所有線程呢

5.前面的線程ID是10進制的,,需要轉換成16進制,,因為等下在jstack命令取出來的線程ID是16進制的:這裏就隨便選一個線程ID 去轉換了,真實環境肯定是選擇CPU佔用率最高的那個線程,echo “obase=16;6219” | bc

 

 

6.jstack 6167 >threadInfo.txt   信息輸出到文件 然後查看。也可以直接在命令裏面查看

 

7.文件中查找184b的線程ID信息,就可以找到是哪個線程導致的內存佔用過高,同時也能看到具體的代碼位置

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

SpringSecurity(1)—認證+授權代碼實現_貨運

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

認證+授權代碼實現

Spring Security是 一種基於 Spring AOP 和 Servlet 過濾器的安全框架。它提供全面的安全性解決方案,同時在 Web 請求級和方法調用級處理身份確認和授權。

有關認證和授權的理論知識,之前有寫過相關博客。了解權限管理

一、SpringSceurity工作流程

網上找一張圖,覺得畫的挺好的,比較容易理解。不然換的是源碼流程圖很難去理解。

圖片地址 : 地址 可以單機放大看更加清楚

要想理解這張圖建議看下這篇博客,因為這張圖中需要自定義的My…類,在文章中都有說明,所以更好理解點。

Spring Boot Security 詳解

二、認證+授權代碼

這裏只展示一些核心代碼,具體完整代碼放在github上。

1、UserDetails接口

Security 中的用戶接口,我們自定義用戶類要實現該接口, 用於向security中注入當前用戶的姓名密碼,和擁有的角色。同時也包含一些其它信息,比如當前用戶是否過期,

賬號是否鎖定等等。

自己定義User實現這個接口

public class User implements UserDetails {
    private String username;
    private String password;
    private List<Role> roles;
    /**
     * 獲取用戶名
     */
    @Override
    public String getUsername() {
        return username;
    }
    /**
     * 獲取密碼
     */
    @Override
    public String getPassword() {
        return password;
    }
    /**
     * 用戶的權限集, 默認需要添加ROLE_ 前綴
     */
    @Override
    @JsonIgnore
    public List<GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
        }
        return authorities;
    }
    /**
     * 賬戶是否過期
     */
    @Override
    @JsonIgnore
    public boolean isAccountNonExpired() {
        return true;
    }
    /**
     * 賬戶是否鎖定
     */
    @Override
    @JsonIgnore
    public boolean isAccountNonLocked() {
        return true;
    }
    /**
     * 憑證是否過期
     */
    @Override
    @JsonIgnore
    public boolean isCredentialsNonExpired() {
        return true;
    }
    /**
     * 用戶是否可用
     */
    @Override
    public boolean isEnabled() {
        return true;
    }  
}

2、UserDetailsService

Security 中的用戶 Service,自定義用戶服務類需要實現該接口。這個接口只有一個方法需要我們去實現,那就是通過用戶名去獲取用戶信息。這裏也是和數據庫交互獲取

用戶認證和授權信息的地方。

@Service
@Slf4j
public class UserService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        //TODO 正常應該查詢數據庫獲取用戶和用戶的權限
//        User user = userMapper.loadUserByUsername(userName);
//        List<Role> roles = rolesMapper.getRolesByUid(user.getId());
//        user.setRoles(roles);
        log.info("登陸用戶名: {}", userName);
        //通過用戶名查詢到的密碼 密碼肯定是加密過的 這裏明文密碼是 123456
        String password = "e10adc3949ba59abbe56e057f20f883e";
        //用戶對應權限
        List<Role> roles = Lists.newArrayList(new Role(1L, "教師"), new Role(2L, "學生"));
        User user = new User(userName, password, roles);
        return user;
    }
}

注意 這裏的明文密碼是 123456,也就是用戶輸入這個才能完成認證。授權的話當前用戶有兩個角色 教師學生。在下面測試的時候會用到。

3、WebSecurityConfigurerAdapter

它是Spring Security的Java 配置類。創建類SecurityConfiguration繼承 WebSecurityConfigurerAdapter,來對我們應用中所有的安全相關的事項(

所有url,驗證用戶名密碼,表單重定向等)進行控制。

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 1、配置的是認證信息, AuthenticationManagerBuilder 這個類,就是AuthenticationManager的建造者, 我們只需要向這個類中, 配置用戶信息,
     *    就能生成對應的AuthenticationManager, 這個類也提過,是用戶身份的管理者, 是認證的入口, 因此,我們需要通過這個配置,想security提供真實的用戶身份。
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    }
    /**
     * 2、配置Security的認證策略, 每個模塊配置使用and結尾。這個也是最複雜的
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    }
    /**
     * 3、這個配置方法用於配置靜態資源的處理方式,可使用 Ant 匹配規則。就是可以不用認證就可以直接訪問的接口
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
    }
}

完整示例

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;
    /**
     * 密碼驗證器
     */
    @Autowired
    private PassWordEncorder passWordEncorder;
    /**
     * 成功處理器
     */
    @Autowired
    private AuthenctiationSuccessHandler authenctiationSuccessHandler;

    /**
     * 失敗處理器
     */
   @Autowired
   private AuthenctiationFailHandler authenctiationFailHandler;
   /**
    * 向Security注入用戶信息
    */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passWordEncorder);
    }
    /**
     * 配置規則
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //開啟登陸配置
        http.authorizeRequests()
                // 登錄之後就能訪問
                .antMatchers("/no-authorize").authenticated()
                // 登陸后 需要校長角色權限
                .antMatchers("/need-authorize").hasRole("校長")
                // 其他的路徑都是登錄后即可訪問
                .anyRequest().authenticated()
                .and().formLogin()
                // 定義登錄頁面,未登錄時,訪問一個需要登錄之後才能訪問的接口,會自動跳轉到該頁面
                .loginPage("/login_page")
                //登錄成功的處理器
                .successHandler(authenctiationSuccessHandler)
                //登錄失敗的處理器
                .failureHandler(authenctiationFailHandler)
                // 登錄處理接口
                .loginProcessingUrl("/login")
                // 定義登錄時,用戶名的 key,默認為 username
                .usernameParameter("username")
                //定義登錄時,用戶密碼的 key,默認為 password
                .passwordParameter("password").permitAll()
                .and().logout()
                ////和表單登錄相關的接口統統都直接通過
                .permitAll()
                .and().csrf().disable().exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());
    }

    /**
     * 對於/static/  下的路徑都不用認證
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/no-login");
    }

    /**
     * 用戶未認證異常攔截
     */
    @Bean
    AccessDeniedHandler getAccessDeniedHandler() {
        return new AuthenticationAccessDeniedHandler();
    }
}

注意 這裏一共配置了三個路徑用於測試。

1、/no-login 接口不需要認證就可以直接訪問
2、/no-authorize 需要認證 但不需要授權就可以訪問
3、/need-authorize 首先需要認證 認證通過還需要授權 這裏需要校長的角色才可以訪問該接口 但是我們測試用戶只有教師和學生所以沒有權限訪問該接口

下面會針對這個個接口分別進行測試。

三、測試

1、接口提供

@RestController
public class TestController {

    /**
     * 1、不需要登陸就可以訪問
     */
    @RequestMapping(value = "/no-login")
    public ServiceResponse noLogin() {
        return ServiceResponse.success("歡迎訪問不需要登陸接口");
    }
    /**
     * 2、只登陸,不許認證接口
     */
    @RequestMapping(value = "/no-authorize")
    public ServiceResponse needAuthorize(){
        return ServiceResponse.success("登陸了 不用授權");
    }
    /**
     * 3、登陸 + 相關認證接口
     */
    @RequestMapping(value = "/need-authorize")
    public ServiceResponse noAuthorize() {
        return ServiceResponse.success("登陸+授權成功");
    }
    /**
     * @Description: 如果自動跳轉到這個頁面,說明用戶未登錄,返回相應的提示即可
     */
    @RequestMapping("/login_page")
    public ServiceResponse loginPage() {
        return  ServiceResponse.failure("001", "尚未登錄,請登錄!");
    }
}

2、未登錄訪問 no-login 和 no-authorize 接口

no-login接口

很明顯沒有登陸 請求該接口成功!

no-authorize接口

沒有登陸訪問失敗,在上面配置了如果用戶沒有認證的話跳轉到login_page接口,所以這裏返回 ‘尚未登錄,請登錄!’

3、登陸后訪問 no-authorize 和 need-authorize 接口

先登陸

根據上面配置登陸的路徑為 /login 請求參數包括 usernamepassword

注意 這裏需要post請求。

no-authorize 接口

登陸就可以訪問了。

need-authorize 接口

雖然登陸成功了,但是因為該接口需要校長角色,之前給該用戶只配置了教師和學生的角色所以訪問失敗。

參考

1、SpringSide 3 中的安全框架

2、Spring Security 工作原理概覽

3、Spring Boot Security 詳解 很贊

別人罵我胖,我會生氣,因為我心裏承認了我胖。別人說我矮,我就會覺得好笑,因為我心裏知道我不可能矮。這就是我們為什麼會對別人的攻擊生氣。
攻我盾者,乃我內心之矛(17)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

SpringBoot學習筆記(十五:OAuth2 )_網頁設計公司

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

@

目錄

  • 一、OAuth 簡介
    • 1、什麼是OAuth
    • 2、OAuth 角色
    • 3、OAuth 授權流程
    • 4、OAuth授權模式
      • 4.1、授權碼
      • 4.2、隱藏式
      • 4.3、密碼式
      • 4.4、憑證式
  • 二、實踐
    • 1、密碼模式
      • 1.1、授權服務器
        • 1.1.1、依賴
        • 1.1.2、授權服務器配置
        • 1.1.3、Spring Security配置
      • 1.2、資源服務器
        • 1.2.1、資源服務器配置
        • 1.2.2、資源服務接口
      • 1.3、測試
        • 1.3.1、獲取token
        • 1.3.2、使用獲取到的token訪問資源接口
    • 2、授權碼模式
      • 2.1、應用註冊
      • 2.2、具體代碼
      • 2.3、測試

一、OAuth 簡介

1、什麼是OAuth

開放授權(Open Authorization,OAuth)是一種資源提供商用於授權第三方應用代表資源所有者獲取有限訪問權限的授權機制。由於在整個授權過程中,第三方應用都無須觸及用戶的密碼就可以取得部分資源的使用權限,所以OAuth是安全開放的。

例如,用戶想通過 QQ 登錄csdn,這時csdn就是一個第三方應用,csdn要訪問用戶的一些基本信息就需要得到用戶的授權,如果用戶把自己的 QQ 用戶名和密碼告訴csdn,那麼csdn就能訪問用戶的所有數據,井且只有用戶修改密碼才能收回授權,這種授權方式安全隱患很大,如果使用 OAuth ,就能很好地解決這一問題。

OAuth第一個版本誕生於2007年12月,並於2010年4月正式被IETF作為標準發布(編號RFC 5849)。由於OAuth1.0複雜的簽名邏輯以及單一的授權流程存在較大缺陷,隨後標準工作組又推出了 OAuth2.0草案,並在2012年10月正式發布其標準(編號RFC 6749)。OAuth2.0放棄了OAuth1.0中讓開發者感到痛苦的数字簽名和加密方案,使用已經得到驗證並廣泛使用的HTTPS技術作為安全保障手 段。OAuth2.0與OAuth1.0互不兼容,由於OAuth1.0已經基本退出歷史舞台,所以下面提到的OAuth都是指OAuth2.0。

2、OAuth 角色

想要理解OAuth的運行流程,則必須要認識4個重要的角色。

  • Resource Owner:資源所有者,通常指用戶,例如每一個QQ用戶。
  • Resource Server:資源服務器,指存放用戶受保護資源的服務器,通常需要通過Access Token(訪問令牌)才能進行訪問。例如,存儲QQ用戶基本信息的服務器,充當的便是資源服務器的 角色。
  • Client:客戶端,指需要獲取用戶資源的第三方應用,如CSDN網站。
  • Authorization Server:授權服務器,用於驗證資源所有者,並在驗證成功之後向客戶端發放相關訪問令牌。

3、OAuth 授權流程

這是 個大致的流程,因為 OAuth2 中有 種不同的授權模式,每種授權模式的授權流程又會有差異,基本流程如下:

  • 客戶端(第三方應用)向資源所有者請求授權。
  • 服務端返回一個授權許可憑證給客戶端。
  • 客戶端拿着授權許可憑證去授權服務器申請令牌。
  • 授權服務器驗證信息無誤后,發放令牌給客戶端。
  • 客戶端拿着令牌去資源服務器訪問資源。
  • 資源服務器驗證令牌無誤后開放資源。

4、OAuth授權模式

OAuth 協議的授權模式共分為4種。

4.1、授權碼

授權碼(authorization code)方式,指的是第三方應用先申請一個授權碼,然後再用該碼獲取令牌。

這種方式是最常用的流程,安全性也最高,它適用於那些有後端的 Web 應用。授權碼通過前端傳送,令牌則是儲存在後端,而且所有與資源服務器的通信都在後端完成。這樣的前後端分離,可以避免令牌泄漏。

  • 第一步,A 網站提供一個鏈接,用戶點擊后就會跳轉到 B 網站,授權用戶數據給 A 網站使用。下面就是 A 網站跳轉 B 網站的一個示意鏈接。
https://b.com/oauth/authorize?
  response_type=code&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=read

上面 URL 中,response_type參數表示要求返回授權碼(code),client_id參數讓 B 知道是誰在請求,redirect_uri參數是 B 接受或拒絕請求后的跳轉網址,scope參數表示要求的授權範圍(這裡是只讀)。

  • 第二步,用戶跳轉后,B 網站會要求用戶登錄,然後詢問是否同意給予 A 網站授權。用戶表示同意,這時 B 網站就會跳回redirect_uri參數指定的網址。跳轉時,會傳回一個授權碼,就像下面這樣。
https://a.com/callback?code=AUTHORIZATION_CODE

上面 URL 中,code參數就是授權碼。

  • 第三步,A 網站拿到授權碼以後,就可以在後端,向 B 網站請求令牌。
https://b.com/oauth/token?
 client_id=CLIENT_ID&
 client_secret=CLIENT_SECRET&
 grant_type=authorization_code&
 code=AUTHORIZATION_CODE&
 redirect_uri=CALLBACK_URL

上面 URL 中,client_id 參數和 client_secret 參數用來讓 B 確認 A 的身份(client_secret參數是保密的,因此只能在後端發請求),grant_type參數的值是 AUTHORIZATION_CODE,表示採用的授權方式是授權碼,code參數是上一步拿到的授權碼,redirect_uri 參數是令牌頒發后的回調網址。

  • 第四步,B 網站收到請求以後,就會頒發令牌。具體做法是向redirect_uri指定的網址,發送一段 JSON 數據。

    {    
      "access_token":"ACCESS_TOKEN",
      "token_type":"bearer",
      "expires_in":2592000,
      "refresh_token":"REFRESH_TOKEN",
      "scope":"read",
      "uid":100101,
      "info":{...}
    }

上面 JSON 數據中,access_token字段就是令牌,A 網站在後端拿到了。

4.2、隱藏式

有些 Web 應用是純前端應用,沒有後端。這時就不能用上面的方式了,必須將令牌儲存在前端。RFC 6749 就規定了第二種方式,允許直接向前端頒發令牌。這種方式沒有授權碼這个中間步驟,所以稱為(授權碼)”隱藏式”(implicit)。

  • 第一步,A 網站提供一個鏈接,要求用戶跳轉到 B 網站,授權用戶數據給 A 網站使用。
https://b.com/oauth/authorize?
  response_type=token&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=read

上面 URL 中,response_type參數為token,表示要求直接返回令牌。

  • 第二步,用戶跳轉到 B 網站,登錄后同意給予 A 網站授權。這時,B 網站就會跳回redirect_uri參數指定的跳轉網址,並且把令牌作為 URL 參數,傳給 A 網站。
https://a.com/callback#token=ACCESS_TOKEN

上面 URL 中,token參數就是令牌,A 網站因此直接在前端拿到令牌。

注意,令牌的位置是 URL 錨點(fragment),而不是查詢字符串(querystring),這是因為 OAuth 2.0 允許跳轉網址是 HTTP 協議,因此存在”中間人攻擊”的風險,而瀏覽器跳轉時,錨點不會發到服務器,就減少了泄漏令牌的風險。

這種方式把令牌直接傳給前端,是很不安全的。因此,只能用於一些安全要求不高的場景,並且令牌的有效期必須非常短,通常就是會話期間(session)有效,瀏覽器關掉,令牌就失效了。

4.3、密碼式

如果你高度信任某個應用,RFC 6749 也允許用戶把用戶名和密碼,直接告訴該應用。該應用就使用你的密碼,申請令牌,這種方式稱為”密碼式”(password)。

  • 第一步,A 網站要求用戶提供 B 網站的用戶名和密碼。拿到以後,A 就直接向 B 請求令牌。
https://oauth.b.com/token?
  grant_type=password&
  username=USERNAME&
  password=PASSWORD&
  client_id=CLIENT_ID

上面 URL 中,grant_type參數是授權方式,這裏的password表示”密碼式”,username和password是 B 的用戶名和密碼。

  • 第二步,B 網站驗證身份通過後,直接給出令牌。注意,這時不需要跳轉,而是把令牌放在 JSON 數據裏面,作為 HTTP 回應,A 因此拿到令牌。

4.4、憑證式

最後一種方式是憑證式(client credentials),適用於沒有前端的命令行應用,即在命令行下請求令牌。

  • 第一步,A 應用在命令行向 B 發出請求。
https://oauth.b.com/token?
  grant_type=client_credentials&
  client_id=CLIENT_ID&
  client_secret=CLIENT_SECRET

上面 URL 中,grant_type參數等於client_credentials表示採用憑證式,client_id和client_secret用來讓 B 確認 A 的身份。

  • 第二步,B 網站驗證通過以後,直接返回令牌。

這種方式給出的令牌,是針對第三方應用的,而不是針對用戶的,即有可能多個用戶共享同一個令牌。

二、實踐

1、密碼模式

如果是自建單點服務,一般都會使用密碼模式。資源服務器和授權服務器
可以是同一台服務器,也可以分開。這裏我們學習分佈式的情況。

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

授權服務器和資源服務器分開,項目結構如下:

1.1、授權服務器

授權服務器的職責:

  • 管理客戶端及其授權信息
    * 管理用戶及其授權信息
    * 管理Token的生成及其存儲
    * 管理Token的校驗及校驗Key

1.1.1、依賴

        <!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--oauth2-->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.6.RELEASE</version>
        </dependency>

1.1.2、授權服務器配置

授權服務器配置通過繼承AuthorizationServerConfigurerAdapter的配置類實現:

/**
 * @Author 三分惡
 * @Date 2020/5/20
 * @Description  授權服務器配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;//密碼模式需要注入認證管理器

    @Autowired
    public PasswordEncoder passwordEncoder;

    //配置客戶端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client-demo")
                .secret(passwordEncoder.encode("123"))
                .authorizedGrantTypes("password") //這裏配置為密碼模式
                .scopes("read_scope");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);//密碼模式必須添加authenticationManager
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .checkTokenAccess("isAuthenticated()");
    }
}

  • 客戶端的註冊:這裏通過inMemory的方式在內存中註冊客戶端相關信息;實際項目中可以通過一些管理接口及界面動態實現客戶端的註冊
  • 校驗Token權限控制:資源服務器如果需要調用授權服務器的/oauth/check_token接口校驗token有效性,那麼需要配置checkTokenAccess(“isAuthenticated()”)
  • authenticationManager配置:需要通過endpoints.authenticationManager(authenticationManager)將Security中的authenticationManager配置到Endpoints中,否則,在Spring Security中配置的權限控制將不會在進行OAuth2相關權限控制的校驗時生效。

1.1.3、Spring Security配置

通過Spring Security來完成用戶及密碼加解密等配置:

/**
 * @Author 三分惡
 * @Date 2020/5/20
 * @Description SpringSecurity 配置
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("fighter")
                .password(passwordEncoder().encode("123"))
                .authorities(new ArrayList<>(0));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //所有請求必須認證
        http.authorizeRequests().anyRequest().authenticated();
    }
}

1.2、資源服務器

資源服務器的職責:

  • token的校驗
  • 給與資源

1.2.1、資源服務器配置

資源服務器依賴一樣,而配置則通過繼承自ResourceServerConfigurerAdapter的配置類來實現:

/**
 * @Author 三分惡
 * @Date 2020/5/20
 * @Description
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Bean
    public RemoteTokenServices remoteTokenServices() {
        final RemoteTokenServices tokenServices = new RemoteTokenServices();
        tokenServices.setClientId("client-demo");
        tokenServices.setClientSecret("123");
        tokenServices.setCheckTokenEndpointUrl("http://localhost:8090/oauth/check_token");
        return tokenServices;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        //session創建策略
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        //所有請求需要認證
        http.authorizeRequests().anyRequest().authenticated();
    }
}

主要進行了如下配置:

  • TokenService配置:在不採用JWT的情況下,需要配置RemoteTokenServices來充當tokenServices,它主要完成Token的校驗等工作。因此需要指定校驗Token的授權服務器接口地址
  • 同時,由於在授權服務器中配置了/oauth/check_token需要客戶端登錄后才能訪問,因此也需要配置客戶端編號及Secret;在校驗之前先進行登錄
  • 通過ResourceServerSecurityConfigurer來配置需要訪問的資源編號及使用的TokenServices

1.2.2、資源服務接口

接口比較簡單:

/**
 * @Author 三分惡
 * @Date 2020/5/20
 * @Description
 */
@RestController
public class ResourceController {

    @GetMapping("/user/{username}")
    public String user(@PathVariable String username){
        return "Hello !"+username;
    }
}

1.3、測試

授權服務器使用8090端口啟動,資源服務器使用默認端口。

1.3.1、獲取token

訪問/oauth/token端點,獲取token:

  • url:   http://localhost:8090/oauth/token?username=fighter&password=123&scope=read_scope&grant_type=password
  • 請求頭:
  • 返回的token

1.3.2、使用獲取到的token訪問資源接口

  • 使用token調用資源,訪問http://localhost:8080/user/fighter,注意使用token添加Bearer請求頭

相當於在Headers中添加 Authorization:Bearer 4a3c351d-770d-42aa-af39-3f54b50152e9。

OK,可以看到資源正確返回。

這裏僅僅是密碼模式的精簡化配置,在實際項目中,某些部分如:

  • 資源服務訪問授權服務去校驗token這部分可能會換成Jwt、Redis等tokenStore實現,
  • 授權服務器中的用戶信息與客戶端信息生產環境從數據庫中讀取,對應Spring Security的UserDetailsService實現類或用戶信息的Provider

2、授權碼模式

很多網站登錄時,允許使用第三方網站的身份,這稱為”第三方登錄”。所謂第三方登錄,實質就是 OAuth 授權。

例如用戶想要登錄 A 網站,A 網站讓用戶提供第三方網站的數據,證明自己的身份。獲取第三方網站的身份數據,就需要 OAuth 授權。

以A網站使用GitHub第三方登錄為例,流程示意如下:

接下來,簡單地實現GitHub登錄流程。

2.1、應用註冊

在使用之前需要先註冊一個應用,讓GitHub可以識別。

  • 訪問地址:https://github.com/settings/applications/new,填寫註冊表

應用的名稱隨便填,主頁 URL 填寫http://localhost:8080,回調地址填寫 http://localhost:8080/oauth/redirect。

  • 提交表單以後,GitHub 應該會返回客戶端 ID(client ID)和客戶端密鑰(client secret),這就是應用的身份識別碼

2.2、具體代碼

  • 只需要引入web依賴:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
  • GitHub相關配置
github.client.clientId=29d127aa0753c12263d7
github.client.clientSecret=f3cb9222961efe4c2adccd6d3e0df706972fa5eb
github.client.authorizeUrl=https://github.com/login/oauth/authorize
github.client.accessTokenUrl=https://github.com/login/oauth/access_token
github.client.redirectUrl=http://localhost:8080/oauth/redirect
github.client.userInfoUrl=https://api.github.com/user

  • 對應的配置類
@Component
@ConfigurationProperties(prefix = "github.client")
public class GithubProperties {
    private String clientId;
    private String clientSecret;
    private String authorizeUrl;
    private String redirectUrl;
    private String accessTokenUrl;
    private String userInfoUrl;
    //省略getter、setter
}    
  • index.html:首頁比較簡單,一個鏈接向後端發起登錄請求
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>網站首頁</title>
</head>
<body>
    <div style="text-align: center">
        <a href="http://localhost:8080/authorize">Login in with GitHub</a>
    </div>
</body>
</html>
  • GithubLoginController.java:
     * 使用RestTemplate發送http請求
     * 使用Jackson解析返回的json,不用引入更多依賴
     * 快捷起見,發送http請求的方法直接寫在控制器中,實際上應該將工具方法分離出去
     * 同樣是快捷起見,返回的用戶信息沒有做任何解析
@Controller
public class GithubLoginController {
    @Autowired
    GithubProperties githubProperties;


    /**
     * 登錄接口,重定向至github
     *
     * @return 跳轉url
     */
    @GetMapping("/authorize")
    public String authorize() {
        String url =githubProperties.getAuthorizeUrl() +
                "?client_id=" + githubProperties.getClientId() +
                "&redirect_uri=" + githubProperties.getRedirectUrl();
        return "redirect:" + url;
    }

    /**
     * 回調接口,用戶同意授權后,GitHub會將授權碼傳遞給此接口
     * @param code GitHub重定向時附加的授權碼,只能用一次
     * @return
     */
    @GetMapping("/oauth/redirect")
    @ResponseBody
    public String redirect(@RequestParam("code") String code) throws JsonProcessingException {
        System.out.println("code:"+code);
        // 使用code獲取token
        String accessToken = this.getAccessToken(code);
        // 使用token獲取userInfo
        String userInfo = this.getUserInfo(accessToken);
        return userInfo;
    }


    /**
     * 使用授權碼獲取token
     * @param code
     * @return
     */
    private String getAccessToken(String code) throws JsonProcessingException {
        String url = githubProperties.getAccessTokenUrl() +
                "?client_id=" + githubProperties.getClientId() +
                "&client_secret=" + githubProperties.getClientSecret() +
                "&code=" + code +
                "&grant_type=authorization_code";
        // 構建請求頭
        HttpHeaders requestHeaders = new HttpHeaders();
        // 指定響應返回json格式
        requestHeaders.add("accept", "application/json");
        // 構建請求實體
        HttpEntity<String> requestEntity = new HttpEntity<>(requestHeaders);
        RestTemplate restTemplate = new RestTemplate();
        // post 請求方式
        ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
        String responseStr = response.getBody();
        // 解析響應json字符串
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseStr);
        String accessToken = jsonNode.get("access_token").asText();
        System.out.println("accessToken:"+accessToken);
        return accessToken;
    }

    /**
     *
     * @param accessToken 使用token獲取userInfo
     * @return
     */
    private String getUserInfo(String accessToken) {
        String url = githubProperties.getUserInfoUrl();
        // 構建請求頭
        HttpHeaders requestHeaders = new HttpHeaders();
        // 指定響應返回json格式
        requestHeaders.add("accept", "application/json");
        // AccessToken放在請求頭中
        requestHeaders.add("Authorization", "token " + accessToken);
        // 構建請求實體
        HttpEntity<String> requestEntity = new HttpEntity<>(requestHeaders);
        RestTemplate restTemplate = new RestTemplate();
        // get請求方式
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
        String userInfo = response.getBody();
        System.out.println("userInfo:"+userInfo);
        return userInfo;
    }

}

2.3、測試

  • 訪問localhost:8080,點擊鏈接,重定向至GitHub
  • 在GitHub中輸入賬號密碼,登錄
  • 登錄成功后,GitHub 就會跳轉到redirect_uri指定的跳轉網址,並且帶上授權碼
http://localhost:8080/oauth/redirect?code=d45683eded3ac7d4e6ed

OK,用戶信息也一併返回了。

本文為學習筆記類博客,學習資料見參考!

參考:

【1】:《SpringSecurity 實戰》
【2】:《SpringBoot Vue全棧開發實戰》
【3】:理解OAuth 2.0
【4】:OAuth 2.0 的一個簡單解釋
【5】:OAuth 2.0 的四種方式
【6】:這個案例寫出來,還怕跟面試官扯不明白 OAuth2 登錄流程?
【7】:做微服務繞不過的 OAuth2,松哥也來和大家扯一扯
【8】:GitHub OAuth 第三方登錄示例教程
【9】:OAuth 2.0 認證的原理與實踐
【10】:Spring Security OAuth2 Demo —— 密碼模式(Password)
【11】:Spring Security OAuth專題學習-密碼模式及客戶端模式實例
【12】:Spring Boot and OAuth2
【13】:Spring Boot+OAuth2使用GitHub登錄自己的服務

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

Diffie-Hellman密鑰協商算法_租車

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

概述

DH算法是非對稱加密算法的鼻祖,為非對稱加密算法奠定了基礎,主要用途是進行密鑰交換確保共享的密鑰能夠安全穿越不安全的網絡。該算法其背後有對應數學理論做支撐,簡單來講就是構造一個複雜的計算難題,使得對該問題的求解在現實的時間內無法快速有效的求解(computationally infeasible )。

這個機制的巧妙在於需要安全通信的雙方可以用這個方法確定對稱密鑰。然後可以用這個對稱密鑰進行加密和解密。但是注意,這個密鑰交換協議/算法只能用於密鑰的交換,而不能進行消息的加密和解密。之所以如此,主要還是由於對稱加密和非對稱加密算法的特性決定的。

1. 對稱加密算法和非對稱加密算法

對稱加密算法

雙方使用的同一個密鑰,既可以加密又可以解密,這種加密方法稱為對稱加密,也稱為單密鑰加密。

優點:速度快,對稱性加密通常在消息發送方需要加密大量數據時使用,算法公開、計算量小、加密速度快、加密效率高。

缺點:在數據傳送前,發送方和接收方必須商定好秘鑰,然後 使雙方都能保存好秘鑰。其次如果一方的秘鑰被泄露,那麼加密信息也就不安全了。另外,每對用戶每次使用對稱加密算法時,都需要使用其他人不知道的唯一秘鑰,這會使得收、發雙方所擁有的鑰匙數量巨大,密鑰管理成為雙方的負擔。

在對稱加密算法中常用的算法有:DES、AES等。

AES:密鑰的長度可以為128、192和256位,也就是16個字節、24個字節和32個字節。

DES:密鑰的長度64位,8個字節。

非對稱加密算法

一對密鑰由公鑰和私鑰組成(可以使用很多對密鑰)。私鑰解密公鑰加密數據,公鑰解密私鑰加密數據(私鑰公鑰可以互相加密解密)。私鑰只能由一方保管,不能外泄。公鑰可以交給任何請求方。

優點:安全。

缺點:速度較慢。

在非對稱加密算法中常用的算法有: DH,RSA等。

兩者區別

  • 算法複雜度:對稱密鑰<非對稱密鑰
  • 加解密速度:對稱密鑰>非對稱密鑰
  • 安全性:對稱密鑰<非對稱密鑰

對稱加密算法相比非對稱加密算法來說,加解密的效率要高得多。但是缺陷在於對於秘鑰的管理上,以及在非安全信道中通訊時,密鑰交換的安全性不能保障。所以在實際的網絡環境中,會將兩者混合使用。因此 ,在https中(TLS\SSL)握手階段使用非對稱加密進行對稱密鑰的協商,而後在後續正常的數據傳輸時,都會使用對稱加密算法進行加密傳輸。

數學基礎

本原根:如果使得 \(a^{m}\equiv 1\ \left( mod\ n \right)\) 成立的最小正冪 \(m\) 滿足 $m=\varphi \left( n \right) $ ,則稱 \(a\)\(n\) 的本原根。 其中 \(\varphi \left( n \right)\) 為歐拉函數。

性質:若 \(a\) 為模 \(n\) 的本原根,則 \(a\)\(a\) 的平方,\(a\) 的3次方,……,\(a\)\(\varphi \left( n \right)\) 次m方 模 \(n\) 的餘數互不相同,而且構成一個模n的簡化剩餘系。

栗子(原根):設 \(n=7\),則 \(\varphi \left( 7 \right) =7\times \left( 1-\frac{1}{7} \right) =6\)

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

  • \(n=2\) 時,我們需要找到一個 \(m\), 使得 \(2^m\%7=1\),且剛好 \(m==\varphi \left( 7 \right)\),首先我們能找到當 \(m=3\) 時,\(2^3\%7=1\),但是卻不等於 \(\varphi \left( 7 \right)\),因此不滿足。
  • \(n=3\)時,類比上面一點,可以發現 \(m=6\) 時,兩點都能滿足。因此 \(3\)\(7\) 的一個原根。

question:找了很多資料對原本根和原根的區別,還是很含糊!有大神的話,希望能教教我,感謝啦!

算法流程及原理

假設Alice需要與Bob協商一個秘鑰(秘鑰本質上就是一個比特序列,從計算的角度看就是一個大數)。

  1. 首先Alice與Bob共享一個素數 \(p\) 以及該素數 \(p\) 的本原根 \(g\) (generator),且 \(2\le g\le p-1\)。這兩個數可以不經過加密的由一方發送到另一方,至於誰發送給誰並不重要,只要保證雙方都能知道 \(p\)\(g\) 即可。
  1. 而後Alice產生一個私有的隨機數 \(A\),滿足 \(1\le A\le p-1\),然後計算 \(g^Amod\ p=Y_a\),將結果 \(Y_a\) 通過公網發送給Bob; 與此同時,Bob也產生一個私有的隨機數 \(B\),滿足 \(1\le B\le p-1\),計算 \(g^Bmod\ p=Y_b\),再將結果 \(Y_b\) 通過公網發送給Alice。
  1. 此時Alice知道的信息有\(p\),\(g\),\(A\),\(Y_a\),\(Y_b\),其中数字 \(A\) 是Alice私有的,只有她自己知道,其他的信息都是別人可能知道的;同樣Bob知道的信息有\(p\),\(g\),\(A\),\(Y_a\),\(Y_b\),其中数字 \(B\) 是Bob私有的,只有自己知道。

    到目前為止,Alice和Bob之間的密鑰協商結束。

    Alice通過計算 \(K_a=\left( Y_b \right) ^A\ mod\ p\) 得到密鑰 \(K_a\),同樣的,Bob通過計算 \(K_b=\left( Y_a \right) ^B\ mod\ p\) 得到密鑰 \(K_b\),可以證明,必然滿足 \(K_a=K_b\)。由此,Alice和Bob得到了相同的密鑰,達成了密鑰協商的目的。

    證明:\(K_a=K_b\)

    對於Alice有:

    \(K_a=\left( Y_b \right) ^Amod\ p=\left( g^Bmod\ p \right) ^A\ mod\ p=g^{B\times A}\ mod\ p\)

    對於Bob有:

    \(K_b=\left( Y_a \right) ^Bmod\ p=\left( g^Amod\ p \right) ^B\ mod\ p=g^{A\times B}\ mod\ p\)

    上面的運算過程,可根據取模運算的性質可得【a ^ b % p = ((a % p)^b) % p】,由上可得,Alice和Bob生成的密鑰其實是進行相同的運算過程,因此必然有 \(K_a=K_b\)

  2. 如果存在一個竊聽者Eve,他能否破解密鑰呢?很顯然Eve能夠竊聽到 \(p\),\(g\),\(Y_a\),\(Y_b\),所以問題轉變了,Eve能否根據這些信息計算出 \(K_a\)\(K_b\) ?要計算 \(K_a\)\(K_b\) 需要知道A和B。

    以計算A為例,Eve能根據 \(p\),\(g\),\(Y_a\) 通過 \(g^Amod\ p=Y_a\) 計算出 \(A\) 嗎? 目前所知的最佳算法Pollard’s rho algorithm for logarithms 時間複雜度是 \(O\left( \sqrt{p} \right)\), 但是實際應用中的 \(p\) 為二進制,假設這個 \(p\) 的長度為 \(n\), 這個複雜度實際上是 $O\left( 2^{\frac{n}{2}} \right) $,這個一個指數級的複雜度,是非常高的。因此求解該問題在計算上的困難程度保證了DH算法的安全性。

安全性問題

那DH密鑰協商算法是否就一定安全呢?我們所熟知的,存在一種偽裝者攻擊(中間人攻擊)能夠對這種密鑰協商算法造成威脅。

假設密鑰協商過程中,在Alice和Bob有一個稱為Alan的主動攻擊者,他能夠截獲Alice和Bob的消息並偽造假消息,考慮以下情況。

  1. Alice和Bob已經共享一個素數 \(p\) 以及其該素數 \(p\) 的本原根 \(g\),當然Alan也監聽到報文得知了這個兩個消息。
  2. 此時Alice計算 \(g^Amod\ p=Y_a\) ,然而將 \(Y_a\) 發送給Bob的過程中被Alan攔截了,Alan自己選定了一個隨機數 \(C\), 計算 \(Y_c=g^C\ mod\ p\), 然後將 \(Y_c\) 發送給Bob。
  1. 同時Bob計算 \(g^Bmod\ p=Y_b\),同樣在將 \(Y_b\) 發送給Alice的過程中被Alan攔截下來,Alan自己選定了一個隨機數D ,計算 \(g^Dmod\ p=Y_d\) 發送給了Alice
  1. 由於Alice和Bob通訊的消息被替換,Alice計算出來的密鑰實際上為Alice和Alan之間協商的密鑰:\(K_{ad}=g^{D\times A}\ mod\ p\);Bob計算出來的密鑰實際上是Blob和Alan之間協商的密鑰:\(K_{bc}=g^{C\times B}\ mod\ p\) 。如果之後Alice和Bob用他們各自計算出來的密鑰加密任何信息,Alan截獲之後都能夠解密得到明文,而Alan也能夠偽裝成Alice或者Bob給雙方發消息,而不至於被發現。

代碼為java實現。

參考

https://www.cnblogs.com/qcblog/p/9016704.html

https://www.cnblogs.com/wushaopei/p/11979200.html

https://blog.csdn.net/l18339702017/article/details/81625257

https://www.jiamisoft.com/blog/24603-dcfdcjm.html

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

結合 AOP 輕鬆處理事件發布處理日誌_包裝設計

※產品缺大量曝光嗎?你需要的是一流包裝設計!

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

結合 AOP 輕鬆處理事件發布處理日誌

Intro

前段時間,實現了 EventBus 以及 EventQueue 基於 Event 的事件處理,但是沒有做日誌(EventLog)相關的部分,原本想增加兩個接口, 處理事件發布日誌和事件處理日誌,最近用了 AOP 的思想處理了 EntityFramework 的數據變更自動審計,於是想着事件日誌也用 AOP 的思想來實現,而且可能用 AOP 來處理可能會更好一些,最近自己造了一個 AOP 的輪子 —— FluentAspects,下面的示例就以它來演示了,你也可以換成自己喜歡的 AOP 組件,思想是類似的

事件日誌示例

事件發布日誌

事件發布日誌只需要攔截事件發布的方法調用即可,在發布事件時進行攔截,在攔截器中根據需要進行日誌記錄即可

事件發布者接口定義:

public interface IEventPublisher
{
    /// <summary>
    /// publish an event
    /// </summary>
    /// <typeparam name="TEvent">event type</typeparam>
    /// <param name="event">event data</param>
    /// <returns>whether the operation succeed</returns>
    bool Publish<TEvent>(TEvent @event) where TEvent : class, IEventBase;

    /// <summary>
    /// publish an event async
    /// </summary>
    /// <typeparam name="TEvent">event type</typeparam>
    /// <param name="event">event data</param>
    /// <returns>whether the operation succeed</returns>
    Task<bool> PublishAsync<TEvent>(TEvent @event) where TEvent : class, IEventBase;
}

事件發布日誌攔截器:

public class EventPublishLogInterceptor : AbstractInterceptor
{
    public override async Task Invoke(IInvocation invocation, Func<Task> next)
    {
        Console.WriteLine("-------------------------------");
        Console.WriteLine($"Event publish begin, eventData:{invocation.Arguments.ToJson()}");
        var watch = Stopwatch.StartNew();
        try
        {
            await next();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Event publish exception({ex})");
        }
        finally
        {
            watch.Stop();
            Console.WriteLine($"Event publish complete, elasped:{watch.ElapsedMilliseconds} ms");
        }
        Console.WriteLine("-------------------------------");
    }
}

事件處理日誌

事件處理器接口定義:

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

public interface IEventHandler
{
    Task Handle(object eventData);
}

事件處理日誌攔截器定義:

public class EventHandleLogInterceptor : IInterceptor
{
    public async Task Invoke(IInvocation invocation, Func<Task> next)
    {
        Console.WriteLine("-------------------------------");
        Console.WriteLine($"Event handle begin, eventData:{invocation.Arguments.ToJson()}");
        var watch = Stopwatch.StartNew();
        try
        {
            await next();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Event handle exception({ex})");
        }
        finally
        {
            watch.Stop();
            Console.WriteLine($"Event handle complete, elasped:{watch.ElapsedMilliseconds} ms");
        }
        Console.WriteLine("-------------------------------");
    }
}

AOP 配置

Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(builder =>
    {
        builder.UseStartup<Startup>();
    })
    .UseFluentAspectsServiceProviderFactory(options =>
    {
        // 攔截器配置
        
        // 攔截 `IEventPublisher` 日誌,註冊事件發布日誌攔截器
        options
            .InterceptType<IEventPublisher>()
            .With<EventPublishLogInterceptor>();

        // 攔截 `IEventHandler`,註冊事件處理日誌攔截器
        options.InterceptType<IEventHandler>()
            .With<EventHandleLogInterceptor>();
    }, builder =>
    {
        // 默認使用默認實現來生成代理,現在提供了 Castle 和 AspectCore 的擴展,也可以自己擴展實現自定義代理生成方式
        // 取消註釋使用 Castle 來生成代理
        //builder.UseCastleProxy();
    }, t => t.Namespace?.StartsWith("WeihanLi") == false // 要忽略的類型斷言
    )
    .Build()
    .Run();

More

事件發布示例,定義了一個發布事件的中間件:

// pageView middleware
app.Use((context, next) =>
{
    var eventPublisher = context.RequestServices
        .GetRequiredService<IEventPublisher>();
    eventPublisher.Publish(new PageViewEvent()
    {
        Path = context.Request.Path.Value,
    });

    return next();
});

事件處理示例是用一個消息隊列的模式來處理的,示例和前面的事件的文章類似,EventConsumer 是一個後台任務,完整代碼示例如下:

public class EventConsumer : BackgroundService
{
    private readonly IEventQueue _eventQueue;
    private readonly IEventHandlerFactory _eventHandlerFactory;

    public EventConsumer(IEventQueue eventQueue, IEventHandlerFactory eventHandlerFactory)
    {
        _eventQueue = eventQueue;
        _eventHandlerFactory = eventHandlerFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var queues = await _eventQueue.GetQueuesAsync();
            if (queues.Count > 0)
            {
                await queues.Select(async q =>
                        {
                            var @event = await _eventQueue.DequeueAsync(q);
                            if (null != @event)
                            {
                                var handlers = _eventHandlerFactory.GetHandlers(@event.GetType());
                                if (handlers.Count > 0)
                                {
                                    await handlers
                                            .Select(h => h.Handle(@event))
                                            .WhenAll()
                                        ;
                                }
                            }
                        })
                        .WhenAll()
                    ;
            }

            await Task.Delay(1000, stoppingToken);
        }
    }
}

完整的示例代碼可以從https://github.com/WeihanLi/WeihanLi.Common/blob/dev/samples/AspNetCoreSample 獲取

OverMore

之前在微軟的 EShopOnContainers 項目里又看到類似下面這樣的代碼,在發布事件的時候包裝一層 try … catch 來記錄事件發布日誌,相比之下,本文示例中的這種方式更為簡潔,代碼更清爽

Reference

  • https://www.cnblogs.com/weihanli/p/12941919.html
  • https://www.cnblogs.com/weihanli/p/implement-event-queue.html
  • https://github.com/WeihanLi/WeihanLi.Common
  • https://github.com/WeihanLi/WeihanLi.Common/blob/dev/samples/AspNetCoreSample/Startup.cs

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

ansible取出register變量中最長字符串_台中搬家

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

背景

在用ansible撰寫一個etcd恢復的playbook時,有一個操作是獲取etcd啟動時的”initial-cluster”啟動參數,該參數在etcd集群不同節點不一致,需要取出etcd節點啟動參數中最長的作為etcdctl snapshot restore的參數。

[root@tke-init ansible]# cat etcd.hosts 
[etcd]
10.0.32.79
10.0.32.41
10.0.32.97

[snapshot]
10.0.32.79 recoverySnapFile=/alauda/etcd_bak/snap-202005250843.db

[root@tke-init ansible]# cat c.yaml 
---
- name: etcd snapshot recovery
  gather_facts: false
  hosts: all
  tasks:
  - name: get the initial-cluster info
    shell: |+
      cat /etc/kubernetes/manifests/etcd.yaml |grep "initial-cluster="|sed 's/.*initial-cluster=//'
    register: initialCluster

  - debug:
      msg: "{{initialCluster.stdout}}"

如下圖,需要取出圈出的最長的字符串。

實現

shell方式

[root@tke-init ansible]# cat c.yaml 
---
- name: etcd snapshot recovery
  gather_facts: false
  hosts: all
  tasks:
  - name: get the initial-cluster info
    shell: |+
      cat /etc/kubernetes/manifests/etcd.yaml |grep "initial-cluster="|sed 's/.*initial-cluster=//'
    register: initialCluster

  - debug:
      msg: "{{initialCluster.stdout}}"
  
  - name: if the /tmp/a.txt exist,remove it 
    file:
      path: /tmp/a.txt
      state: absent
      force: yes
    run_once: true
    delegate_to: localhost 

  - name: echo the all initialCluster parameter to localhost
    shell: |+
      echo "{{item}}" >>/tmp/a.txt
    with_items:
      - "{{ initialCluster.stdout }}"
    delegate_to: localhost

  - name: get the longest initial-cluster paramaters
    shell:
      cat /tmp/a.txt  |awk '{print length($0),$0}'|sort -k1 -rn|head -1|awk '{print $2}'
    register: maxInitialCluster
    run_once: true
    delegate_to: localhost
  - debug:
      msg: "{{ maxInitialCluster.stdout }}"

執行測試如下

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

ansible過濾器方式

[root@tke-init ansible]# cat bb.yaml 
---
- name: test
  gather_facts: false
  hosts: all
  tasks:
  - name: get the initial-cluster info
    shell: |+
      cat /etc/kubernetes/manifests/etcd.yaml |grep "initial-cluster="|sed 's/.*initial-cluster=//'
    register: initialCluster

  - set_fact:
       combined_initialCluster: "{{ groups['etcd'] |map('extract',hostvars,['initialCluster','stdout']) |list |join(',')  }}"

  - set_fact:
      final_initialCluster: "{{ combined_initialCluster.split(',')|unique|join(',') }}"

  - debug:
      var: final_initialCluster

執行測試如下

總結

  1. shell方式來說,雖然比較繞,但是更加通用;ansible過濾器方式,其中有一個unique的filter,只適用本次場景中正好有重複列表元素的情況,如果每個節點的register取回的字符串完全不一致,則無法適用。

  2. 取回全部register的字符串組合成一個list后,原本計劃使用max過濾器取出列表中最長的字符串元素,發現max過濾器無法傳遞key參數,而python原生的max方法是支持傳遞key參數的。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

Shiro (Shiro + JWT + SpringBoot應用)_台中搬家公司

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

Shiro (Shiro + JWT + SpringBoot應用)

目錄

  • Shiro (Shiro + JWT + SpringBoot應用)
    • 1.Shiro的簡介
    • 2.Shiro + JWT + SpringBoot
      • 1.導入依賴
      • 2.配置JWT
      • 3.配置Shiro
      • 4.實現JWTToken
      • 5.實現Realm
      • 6.重寫Filter
      • 7. ShiroConfig
      • 8.登陸
      • 9.@RequiresPermissions

1.Shiro的簡介

Apache Shiro是一種功能強大且易於使用的Java安全框架,它執行身份驗證,授權,加密和會話管理,可用於保護 從命令行應用程序,移動應用程序到Web和企業應用程序等應用的安全。

  1. Authentication 身份認證/登錄,驗證用戶是不是擁有相應的身份;
  2. Authorization 授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具有某個權限;
  3. Cryptography 安全數據加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;
  4. Session Management 會話管理,即用戶登錄后就是一次會話,在沒有退出之前,它的所有信息都在會話中;
  5. Web Integration web系統集成
  6. Interations 集成其它應用,spring、緩存框架

從應用程序角度的來觀察如何使用Shiro完成工作:

Subject:主體,代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等;即一個抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會委託給SecurityManager;可以把Subject認為是一個門面;SecurityManager才是實際的執行者;

SecurityManager:安全管理器;即所有與安全有關的操作都會與SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它負責與後邊介紹的其他組件進行交互,如果學習過SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm:域,Shiro從從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那麼它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源。

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

也就是說對於我們而言,最簡單的一個Shiro應用:

1、應用代碼通過Subject來進行認證和授權,而Subject又委託給SecurityManager;

2、我們需要給Shiro的SecurityManager注入Realm,從而讓SecurityManager能得到合法的用戶及其權限進行判斷。

2.Shiro + JWT + SpringBoot

1.導入依賴
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.1</version>
</dependency>
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.8.2</version>
</dependency>
2.配置JWT
public class JWTUtil {
    /**
     * 校驗 token是否正確
     *
     * @param token  密鑰
     * @param secret 用戶的密碼
     * @return 是否正確
     */
    public static boolean verify(String token, String username, String secret) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            verifier.verify(token);
            return true;
        } catch (Exception e) {
            log.info("token is invalid{}", e.getMessage());
            return false;
        }
    }

    public static String getUsername(HttpServletRequest request) {
        // 取token
        String token = request.getHeader("Authorization");
        return getUsername(UofferUtil.decryptToken(token));
    }
    /**
     * 從 token中獲取用戶名
     * @return token中包含的用戶名
     */
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            log.error("error:{}", e.getMessage());
            return null;
        }
    }
    
    public static Integer getUserId(HttpServletRequest request) {
        // 取token
        String token = request.getHeader("Authorization");
        return getUserId(UofferUtil.decryptToken(token));
    }
    /**
     * 從 token中獲取用戶ID
     * @return token中包含的ID
     */
    public static Integer getUserId(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return Integer.valueOf(jwt.getSubject());
        } catch (JWTDecodeException e) {
            log.error("error:{}", e.getMessage());
            return null;
        }
    }


    /**
     * 生成 token
     * @param username 用戶名
     * @param secret   用戶的密碼
     * @return token 加密的token
     */
    public static String sign(String username, String secret, Integer userId) {
        try {
            Map<String, Object> map = new HashMap<>();
            map.put("alg", "HS256");
            map.put("typ", "JWT");
            username = StringUtils.lowerCase(username);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            return JWT.create()
                    .withHeader(map)
                    .withClaim("username", username)
                    .withSubject(String.valueOf(userId))
                    .withIssuedAt(new Date())
//                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            log.error("error:{}", e);
            return null;
        }
    }
}
3.配置Shiro
4.實現JWTToken

token自己已經包含了用戶名等信息。

@Data
public class JWTToken implements AuthenticationToken {

    private static final long serialVersionUID = 1282057025599826155L;

    private String token;

    private String exipreAt;

    public JWTToken(String token) {
        this.token = token;
    }

    public JWTToken(String token, String exipreAt) {
        this.token = token;
        this.exipreAt = exipreAt;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }

}
5.實現Realm

自定義實現 ShiroRealm,包含認證和授權兩大模塊。

public class ShiroRealm extends AuthorizingRealm {

    @Resource
    private RedisUtil redisUtil;

    @Autowired
    private ISysUserService userService;

    @Autowired
    private ISysRoleService roleService;

    @Autowired
    private ISysMenuService menuService;

    // 必須重寫此方法,不然Shiro會報錯
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 只有當需要檢測用戶權限的時候才會調用此方法
     * 授權模塊,獲取用戶角色和權限。
     * @param token token
     * @return AuthorizationInfo 權限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection token) {
        Integer userId = JWTUtil.getUserId(token.toString());

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        // 獲取用戶角色集
        Set<String> roleSet = roleService.selectRolePermissionByUserId(userId);
        simpleAuthorizationInfo.setRoles(roleSet);

        // 獲取用戶權限集
        Set<String> permissionSet = menuService.findUserPermissionsByUserId(userId);
        simpleAuthorizationInfo.setStringPermissions(permissionSet);
        return simpleAuthorizationInfo;
    }

    /**
     * 用戶認證:編寫shiro判斷邏輯,進行用戶認證
     * @param authenticationToken 身份認證 token
     * @return AuthenticationInfo 身份認證信息
     * @throws AuthenticationException 認證相關異常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 這裏的 token是從 JWTFilter 的 executeLogin 方法傳遞過來的,已經經過了解密
        String token = (String) authenticationToken.getCredentials();
        String encryptToken = UofferUtil.encryptToken(token); //加密token
        String username = JWTUtil.getUsername(token); //從token中獲取username
        Integer userId = JWTUtil.getUserId(token);    //從token中獲取userId

        // 通過redis查看token是否過期
        HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
        String ip = IPUtil.getIpAddr(request);
        String encryptTokenInRedis = redisUtil.get(Constant.RM_TOKEN_CACHE + encryptToken + StringPool.UNDERSCORE + ip);
        if (!token.equalsIgnoreCase(UofferUtil.decryptToken(encryptTokenInRedis))) {
            throw new AuthenticationException("token已經過期");
        }

        // 如果找不到,說明已經失效
        if (StringUtils.isBlank(encryptTokenInRedis)) {
            throw new AuthenticationException("token已經過期");
        }

        if (StringUtils.isBlank(username)) {
            throw new AuthenticationException("token校驗不通過");
        }

        // 通過用戶id查詢用戶信息
        SysUser user = userService.getById(userId);

        if (user == null) {
            throw new AuthenticationException("用戶名或密碼錯誤");
        }
        if (!JWTUtil.verify(token, username, user.getPassword())) {
            throw new AuthenticationException("token校驗不通過");
        }
        return new SimpleAuthenticationInfo(token, token, "febs_shiro_realm");
    }
}
6.重寫Filter

所有的請求都會先經過 Filter,所以我們繼承官方的 BasicHttpAuthenticationFilter ,並且重寫鑒權的方法。

代碼的執行流程 preHandle -> isAccessAllowed -> isLoginAttempt -> executeLogin

@Slf4j
public class JWTFilter extends BasicHttpAuthenticationFilter {

    private static final String TOKEN = "Authorization";

    private AntPathMatcher pathMatcher = new AntPathMatcher();

    /**
     * 對跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域時會首先發送一個 option請求,這裏我們給 option請求直接返回正常狀態
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
    
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        UofferProperties UofferProperties = SpringContextUtil.getBean(UofferProperties.class);
        // 獲取免認證接口 url 
        // 在application.yml中配置/adminApi/auth/doLogin/**,/adminApi/auth/register/**, ...
        String[] anonUrl = StringUtils.splitByWholeSeparatorPreserveAllTokens(UofferProperties.getShiro().getAnonUrl(), ",");

        boolean match = false;
        for (String u : anonUrl) {
            if (pathMatcher.match(u, httpServletRequest.getRequestURI())) {
                match = true;
            }
        }
        if (match) {
            return true;
        }
        if (isLoginAttempt(request, response)) {
            return executeLogin(request, response);
        }
        return false;
    }

    /**
     * 判斷用戶是否想要登入。
     * 檢測header裏面是否包含Authorization字段即可
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader(TOKEN);
        return token != null;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader(TOKEN); //得到token
        JWTToken jwtToken = new JWTToken(UofferUtil.decryptToken(token)); // 解密token
        try {
            // 提交給realm進行登入,如果錯誤他會拋出異常並被捕獲
            getSubject(request, response).login(jwtToken);
            // 如果沒有拋出異常則代表登入成功,返回true
            return true;
        } catch (Exception e) {
            log.error(e.getMessage());
            return false;
        }
    }

    @Override
    protected boolean sendChallenge(ServletRequest request, ServletResponse response) {
        log.debug("Authentication required: sending 401 Authentication challenge response.");
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
//        httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
        httpResponse.setCharacterEncoding("utf-8");
        httpResponse.setContentType("application/json; charset=utf-8");
        final String message = "未認證,請在前端系統進行認證";
        final Integer status = 401;
        try (PrintWriter out = httpResponse.getWriter()) {
//            String responseJson = "{\"message\":\"" + message + "\"}";
            JSONObject responseJson = new JSONObject();
            responseJson.put("msg", message);
            responseJson.put("status", status);
            out.print(responseJson);
        } catch (IOException e) {
            log.error("sendChallenge error:", e);
        }
        return false;
    }



}
7. ShiroConfig
@Configuration
public class ShiroConfig {

    @Bean
    public ShiroRealm shiroRealm() {
        // 配置 Realm
        return new ShiroRealm();
    }
    
    // 創建DefaultWebSecurityManager
    @Bean("securityManager")
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 配置 SecurityManager,並注入 shiroRealm
        securityManager.setRealm(shiroRealm());
        return securityManager;
    }
    
    // 創建ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 設置 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

       	//添加Shiro過濾器
		/**
		 * Shiro內置過濾器,可以實現權限相關的攔截器
		 *    常用的過濾器:
		 *       anon: 無需認證(登錄)可以訪問
		 *       authc: 必須認證才可以訪問
		 *       user: 如果使用rememberMe的功能可以直接訪問
		 *       perms: 該資源必須得到資源權限才可以訪問
		 *       role: 該資源必須得到角色權限才可以訪問
		 */
        
        // 在 Shiro過濾器鏈上加入 自定義過濾器JWTFilter 並取名為jwt
        LinkedHashMap<String, Filter> filters = new LinkedHashMap<>();
        filters.put("jwt", new JWTFilter());
        shiroFilterFactoryBean.setFilters(filters);

        // 自定義url規則
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 所有請求都要經過 jwt過濾器
        filterChainDefinitionMap.put("/**", "jwt");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 下面的代碼是添加註解支持
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        // 設置代理類
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);

        return creator;
    }

    /**
     * 開啟aop註解支持
     *
     * @param securityManager
     * @return
     */
    @Bean("authorizationAttributeSourceAdvisor")
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    
    // Shiro生命周期處理器
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

}

8.登陸
    /**
     * 登錄方法
     *
     * @param username 用戶名
     * @param password 密碼
     * @param code     驗證碼
     * @param uuid     唯一標識
     * @return 結果
     */
    @PostMapping("/doLogin")
    public ResultVo login(String username, String password, String code, String uuid, HttpServletRequest request) throws UofferException {

        String verifyKey = Constant.RM_CAPTCHA_CODE_KEY + uuid;
        String captcha = redisUtil.getCacheObject(verifyKey);
        redisUtil.del(verifyKey);

        if (captcha == null) {
            return ResultVo.failed(201, "驗證碼失效");
        }
        if (!code.equalsIgnoreCase(captcha)) {
            return ResultVo.failed(201, "驗證碼錯誤");
        }

        username = StringUtils.lowerCase(username);
        password = MD5Util.encrypt(username, password);

        final String errorMessage = "用戶名或密碼錯誤";
        SysUser user = userManager.getUser(username);

        if (user == null) {
            return ResultVo.failed(201, errorMessage);
        }
        if (!StringUtils.equalsIgnoreCase(user.getPassword(), password)) {
            return ResultVo.failed(201, errorMessage);
        }
        if (Constant.STATUS_LOCK.equals(user.getStatus())) {
            return ResultVo.failed(201, "賬號已被鎖定,請聯繫管理員!");
        }


        Integer userId = user.getUserId();
        String ip = IPUtil.getIpAddr(request);
        String address = AddressUtil.getCityInfo(ip);
        // 更新用戶登錄時間
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLastLoginTime(new Date());
        sysUser.setLastLoginIp(ip);
        userService.updateById(sysUser);


        // 拿到token之後加密
        String sign = JWTUtil.sign(username, password, userId);
        String token = UofferUtil.encryptToken(sign);
        LocalDateTime expireTime = LocalDateTime.now().plusSeconds(properties.getShiro().getJwtTimeOut());
        String expireTimeStr = DateUtil.formatFullTime(expireTime);
        JWTToken jwtToken = new JWTToken(token, expireTimeStr);

        // 將登錄日誌存入日誌表中
        SysLoginLog loginLog = new SysLoginLog();
        loginLog.setIp(ip);
        loginLog.setAddress(address);
        loginLog.setLoginTime(new Date());
        loginLog.setUsername(username);
        loginLog.setUserId(userId);
        loginLogService.save(loginLog);

        saveTokenToRedis(username, jwtToken, ip, address);
        JSONObject data = new JSONObject();
        data.put("Authorization", token);

        // 將用戶配置及權限存入redis中
        userManager.loadOneUserRedisCache(userId);
        return ResultVo.oK(data);
    }
9.@RequiresPermissions

要求subject中必須含有bus:careerTalk:query的權限才能執行方法someMethod()。否則拋出異常AuthorizationException

@RequiresPermissions("bus:careerTalk:query")
public void someMethod() {
}

引用:
https://www.iteye.com/blog/jinnianshilongnian-2018398
https://www.jianshu.com/p/f37f8c295057

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

月銷2萬!國產SUV又一爆款誕生了!_網頁設計公司

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

5的最低投訴銷量比奪得第一,大幅超越主流合資產品,成為有口皆碑的A級車“品質王”。榮威360憑藉“藍芯”科技在動力總成上的核心優勢,以榮威RX5和榮威360為代表的產品“量價齊收”,還將持續帶動榮威品牌的快速增長。新能源:獲中國汽車界“諾貝爾”獎,勇奪專業賽道“插混王”10月,上汽新能源不僅在技術、整車性能等方面得到了行業和消費者的認可,市場銷量也在不斷攀升。

在榮威RX5強勁熱銷勢頭的帶動下,上汽乘用車10月銷量破4萬,同比激增147%!榮威品牌更是以245%的增速,蟬聯中國品牌增速王!

10月,榮威RX5月銷量首次突破2萬輛,截至目前訂單量已突破10萬輛。第4.5萬輛榮威RX5已於日前在上汽臨港生產基地下線,南京工廠榮威RX5生產線也如期改造完成並將全面投產。榮威RX5的熱銷還帶動了榮威、MG名爵各款車型的銷售,上汽乘用車10月份總銷量超過4萬輛,同比激增147%。

上汽乘用車堅持中高端發展路線,十年磨一劍,通過品質和技術的提升來打造最好的產品。隨着今年“芯動戰略”成果的集中爆發,上汽也正式駛入“品質+銷量”發展階段的“快車道”,引領中國品牌走進品價比時代。

榮威RX5月銷量破2萬 好爸爸聯手領跑市場

10月,榮威RX5銷量突破2萬輛。在榮威RX5熱銷帶動下,上汽榮威品牌從8月同比增長205%,9月增214%,到10月的245%,銷量連續翻番,勢如破竹,帶領上汽乘用車重回發展快車道。爆款SUV榮威RX5上市第三個月銷量即破2萬,還獲得李克強總理親切“接見”,以及工博會工業設計金獎、2017年度德國紅點“中國好設計”榮譽獎、2016“上海市優秀工業設計金獎”等榮譽;榮威360和榮威350組成的“榮威3系”,9月、10月銷量接連破萬,實現了銷量與口碑的雙豐收。

榮威RX5獲工博會工業設計金獎

從上市不到2周互聯網車型訂單破萬,到上市首月交車近萬輛,次月銷量近1.4萬輛,再到第三個月銷量超過2萬輛,榮威RX5的爆款熱銷之勢根本停不下來!作為首款成功突破合資價格區間的中國品牌SUV,真正讓消費者“買得起、用得爽”,成為10-20萬元SUV首選。隨着榮威RX5持續熱賣,勢必將進一步撬動車市格局。

榮威RX5在SUV市場上的大獲全勝,離不開陳虹“爸爸”和馬雲“爸爸”的聯手助攻。在10月的全球首次互聯網汽車拉力賽中,榮威RX5憑藉上汽世界級“藍芯”高效動力科技,以及阿里的互聯網黑科技,創下3.7L/百公里的超低油耗紀錄,刷新SUV節油標準。憑藉互聯網“黑科技”在複雜生活場景中的實際應用,榮威RX5率先開啟了未來交通密碼,為用戶探索追求新生活方式的可能性。

榮威RX5全球首次互聯網汽車拉力賽啟動

不只在SUV市場發力,在家轎市場上,榮威品牌勢頭同樣強勁。上市一周年的榮威360,憑藉全方位高品質和紮實的用戶口碑,累計銷售9萬輛,成為囊括“Carplay車型銷量王”、“品質王”及“A級車節油王”的“三冠王”,更是榮獲德國紅點“中國好設計”唯一汽車類大獎。此前的“千公里一箱油”極限油耗挑戰總決賽中,榮威360一箱油可行駛1496km,創下3.7L/100km的最低油耗紀錄,力證“藍芯”世界級高效動力的領先燃油經濟性。在權威汽車平台《車質網》公布的投訴排行榜中,榮威360以萬分之2.5的最低投訴銷量比奪得第一,大幅超越主流合資產品,成為有口皆碑的A級車“品質王”。

榮威360

憑藉“藍芯”科技在動力總成上的核心優勢,以榮威RX5和榮威360為代表的產品“量價齊收”,

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

還將持續帶動榮威品牌的快速增長。

新能源:獲中國汽車界“諾貝爾”獎,勇奪專業賽道“插混王”

10月,上汽新能源不僅在技術、整車性能等方面得到了行業和消費者的認可,市場銷量也在不斷攀升。

10月28日,中國汽車工業科學技術獎揭曉,上汽新能源憑藉在EDU電驅、HCU電控和ESS電池方面的創新,成為今年插電混動領域唯一獲一等獎的車企,這也是上汽時隔5年再次獲得中國汽車界最高榮譽。中國汽車工業科學技術獎是經國家科委批准,業內公認的最能代表中國汽車科技發展水平的獎項,被譽為中國汽車界“諾貝爾獎”。擁有百餘項國內外專利授權和技術認證,上汽插電混動技術突破了混合動力技術的專利壁壘,成為最先進的新能源汽車動力科技。

榮威e550

得益於上汽世界級新能源科技的運用,榮威e550首次參加中國量產車性能大賽(CCpC),便以總成績第一的表現摘得冠軍,並分別在長距離經濟性、制動性、舒適性等方面共斬獲了6個單項冠軍,充分驗證了上汽新能源領先世界的產品性能,大大增強了消費者的購買信心。

截止10月,上汽新能源今年已累計銷售17474輛,同比大增114%。其中,主銷車型榮威e550共銷售了13600輛,同比增長72%,多次獲得新能源中級車“銷量王”稱號。今年上市的中國首款插電混動高級轎車榮威e950,已連續三個月成為上海B級車銷量冠軍,並成為中國首款月銷過千的新能源B級車。

榮威e950

接下來,上汽新能源還將推出SUV車型,豐富消費者選擇,並引領中高端SUV市場消費趨勢,給消費者提供最智能便捷、最節能環保的出行方案。

MG名爵:前10月同比增長30% 名爵銳騰高性能領跑SUV細分市場

MG名爵10月繼續保持穩步攀升的態勢。今年1-10月,MG名爵全系車型銷量達到58797輛,與去年同比勁增27%!“高性能中級SUV”名爵銳騰更是以全面越級的產品力強勢領跑,同比增長30%。

征戰,不是我們的習慣,而是我們的傳統!作為“高性能中級SUV”,名爵銳騰7月從新疆天山開啟征程,以“十項全能 挑戰巔峰”的高性能姿態全面征服嚴苛複雜的山路環境。得益於上汽世界級“藍芯”動力科技與保時捷專業級底盤調校強強聯手,名爵銳騰從絕壁斷崖到5000米海拔全面制霸,並用自己獨有的方式和態度號召更多年輕人加入到征戰的隊伍中,把自己的愛車真正“開出趣”、綻放年輕的魅力。

名爵銳騰

除了SUV產品的出色表現,高性能風尚中級車MG GT名爵銳行勢頭同樣強勁,1-10月銷量同比增長更是高達61%。今年推出2016款MG GT名爵銳行並新增1.5L車型后,其價格下探至7萬區間,幫助更多年輕消費者實現購車夢想。此外,9月起MG GT名爵銳行的智能娛樂行車系統全面升級,可支持百度CarLife,適配手機的型號更寬泛,名爵銳行也成為首款同時兼容蘋果Carplay和百度CarLife的中級車,全面進入“雙Car”時代。

MG名爵與生俱來的競速基因,讓更多的消費者享受駕控樂趣和汽車生活,綻放獨有的個性魅力,將愛車“開出趣”。即將進入年底收官的關鍵,高性能、高智能、高安全的核心優勢,將帶動MG名爵品牌持續高速增長。

隨着品價比時代的到來,上汽始終堅持品質為王,打造符合時代的最好產品。在“芯動戰略”的整體戰略布局下,上汽乘用車以“藍芯”和“綠芯”兩大技術品牌為支撐,搭載互聯網思維,堅持創新發展,突破傳統局限。未來上汽乘用車還將不斷超越創新,為消費者帶來更多高品質的越級之作。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

9萬級省油耐用到頂點的兩款三廂車 誰最值得買?_如何寫文案

※別再煩惱如何寫文案,掌握八大原則!

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

5升發動機最大馬力107匹。最大功率最大扭矩140牛米。陽光最大馬力112匹最大扭矩139牛米,並且兩台車型都搭配CVT的變速器。在實際的主觀加速感受上兩輛車都不怎麼樣。好在動力系統的平順性十分好。並且在油耗方面兩台車的工信部綜合油耗分別為威馳5。

前不久有個粉絲問小編美美,我現在糾結在豐田的威馳和日產的陽光之間,它們都很省油,而且尺寸也不大比較適合家用,價格也算便宜。很適合我這種經濟能力不高的人。可是這兩個車型有差不多的尺寸,同樣都是1.5升發動機和CVT變速箱,也同樣省油。兩個品牌也都不錯,這兩個車讓我十分糾結我到底應該選哪一個比較好呢?

其實我相信有不少人都糾結在這兩款車之間,那麼小編美美就來給你們進行一個分析,看一看哪一個更加值得購買。

對比車型:

豐田威馳 2017款 1.5L CVT創行版

指導價:8.98萬(下文簡稱威馳)

日產陽光 2015款 1.5XE CVT舒適版

指導價:8.98萬(下文簡稱陽光)

外觀

威馳更加好看

從外觀上來說兩個車型的風格可以說有着比較明顯的區別,陽光是屬於那種非常圓潤,在設計元素以及細節上有一些像日產天籟,不過奇怪的比例以及比較小的輪轂這些因素導致陽光看起來不那麼協調,總感覺怪怪的。直白地說,有不少人都覺得陽光長得不好看。

而一向中庸的豐田在威馳的造型設計上也基本遵循的這個理念,它的比例以及設計都比較的均衡,威馳算不上一台很漂亮的小車,它同樣存在着比較高比較窄的缺點,但是威馳看起來比陽光要均衡很多,不會讓人覺得丑。

內飾

威馳設計更有檔次

在內飾設計上陽光多以圓形元素為主打一種輕鬆活潑的內飾設計風格,這樣的設計比較能夠討好女性消費者,但是並不太符合男性消費者的喜好。內飾的用料全部都是硬塑料,做工也只能算是一般般和這個價位的自主車型相比有着不小的差距。

威馳在內飾上則顯得更加的討巧。雖然它的用料也是硬塑料,

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

但是它的配色更加豐富,而且車內大量的運用了仿真皮縫線的設計,逼真程度還十分高,你如果不是用手去摸光看上去可能會覺得真的就是皮質的,這也給車內的檔次感增色不少。

空間

半斤八兩

如果你仔細觀察兩個車的話,會發現他們的車廂設計都非常的飽滿。在一個小型車的基礎上卻有這一個大大的肚子,這也是為了極力營造車內空間而做出的考量。這樣的設計帶來的好處則是它們的車內空間無論是頭部還是腿部都十分的寬敞,完全達到了緊湊型轎車的水準,但是兩車都受限於僅僅一米七左右的車身寬度。他們的車內橫向空間只能算是一般般。

動力

初段威馳更好

兩款車型都採用1.5升自然吸氣的發動機,其中豐田威馳的1.5升發動機最大馬力107匹。最大功率最大扭矩140牛米。陽光最大馬力112匹最大扭矩139牛米,並且兩台車型都搭配CVT的變速器。在實際的主觀加速感受上兩輛車都不怎麼樣。好在動力系統的平順性十分好。並且在油耗方面兩台車的工信部綜合油耗分別為威馳5.1升/100公里,陽光5.8升/100公里。如果從省油的角度來考慮的話這兩台車的動力系統也算是十分優秀了。

配置

威馳碾壓陽光

這樣兩個各方面都爭鋒相對的對手,在外觀、內飾、空間和動力方面的表現都不相伯仲,那麼配置就成了它們的關鍵競爭點了。在配置上剛剛完成改款的豐田威馳可以說全面碾壓日產陽光,首先威馳有着15寸的輪轂,而陽光為14寸。而威馳相比陽光多出了

牽引力控制系統

車身穩定控制

上坡輔助

座椅高低調節

後排杯架

后視鏡電動調節

發動機啟停

而陽光僅僅多出行車電腦显示屏這一個配置。因此,在配置上豐田威馳可以說碾壓日產陽光。

總結在外觀上威馳要更加的均衡比陽光耐看很多。在內飾的設計上豐田威馳也要更加的雞賊。雖然用料和配置相差不多,但是豐田在內飾設計上確實玩出了花樣,威馳的整個內飾看上去比陽光要高上一個級別。在空間和動力上兩款車型表現十分接近,而在配置上威馳全面碾壓陽光。因此這次pk的結果非常明確那就是豐田威馳完勝。

不過汽車還是一個感性的產品。因此小編美美也想多談一些對兩款車的看法。這兩款車型小編美美都進行過一定程度的體驗。從家用的角度來講陽光的舒適性要更加的好,主要體現在它的底盤的質感比威馳好一些,而且減震器的阻尼設定也要更加的合理科學。確切的說是豐田威馳有一些顛了,日常乘坐在走爛路的時候,舒適性並不咋地,所以小編覺得如果你是一個十分注重舒適性的人。那麼可能日產陽光會更佳的適合你。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※教你寫出一流的銷售文案?

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。