一、BCrypt加密

1、使用BCrypt加密準備工作

(1)、scf_user專案中加入BCrypt依賴

        <!--加入springSecurity依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

(2)、scf_user新增配置類

        scf_user專案中加入springsecurity依賴之後,所有的請求都會被springsecurity控制,我們只需要加密功能,因此,需要新增配置類,配置所有的地址都可以匿名訪問。

package com.scf.user;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 *  安全配置類
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/**").permitAll() //配置所有的請求都可以匿名訪問
                .anyRequest().authenticated()
                .and().csrf().disable();
    }
}

(3)、修改scf_user的啟動引導類,配置bean


2、管理員密碼加密與校驗

AdminDao.java

    /**
     * 根據登入名查詢管理員
     * @param loginname
     * @return
     */
    public Admin findByLoginname(String loginname);

AdminService.java

    @Autowired
    private BCryptPasswordEncoder encoder;

    /**
     * 增加管理員
     * 新增時對密碼進行加密
     *
     * @param admin
     */
    public void add(Admin admin) {
        admin.setId(idWorker.nextId() + "");
        //對密碼進行加密
        String password = encoder.encode(admin.getPassword());
        admin.setPassword(password);
        adminDao.save(admin);
    }

    /**
     * 根據使用者名稱和密碼查詢管理員
     * @param loginname
     * @param password
     * @return
     */
    public Admin findAdminByLoginnameAndPassword(String loginname, String password){
        Admin admin = adminDao.findByLoginname(loginname);
        if(admin != null && encoder.matches(password, admin.getPassword())){
            return admin;
        }else{
            return null;
        }
    }

AdminController.java

	@RequestMapping(value = "/login", method = RequestMethod.POST)
	public Result login(@RequestBody Map<String, String> loginMap){
		Admin admin = adminService.findAdminByLoginnameAndPassword(loginMap.get("loginname"), loginMap.get("password"));
		if(admin != null){
			return new Result(true, StatusCode.OK, "登入成功");
		}else{
			return new Result(false, StatusCode.LOGINERROR, "使用者名稱或者密碼錯誤");
		}
	}

3、使用者密碼加密

UserDao.java

    /**
     * 根據手機號查詢使用者
     * @param mobile
     * @return
     */
    public User findByMobile(String mobile);

UserService.java

    @Autowired
    private BCryptPasswordEncoder encoder;

    /**
     * 註冊使用者
     * @param user
     * @param code
     */
    public void register(User user, String code) {
        //判斷驗證碼是否正確
        String redisCode = (String) redisTemplate.opsForValue().get("smscode_" + user.getMobile());
        if (redisCode == null) {
            throw new RuntimeException("請點選獲取正確的驗證碼!");
        }
        if (!redisCode.equalsIgnoreCase(code)) {
            throw new RuntimeException("請輸入正確的驗證碼!");
        }
        //給密碼加密
        String password = encoder.encode(user.getPassword());
        user.setPassword(password);

        //設定使用者屬性欄位
        user.setId(idWorker.nextId() + "");
        user.setFollowcount(0);//關注數
        user.setFanscount(0);//粉絲數
        user.setOnline(0L);//線上時長
        user.setRegdate(new Date());//註冊時間
        user.setUpdatedate(new Date());//修改時間
        user.setLastdate(new Date());//最後登入時間
        userDao.save(user);
    }

    /**
     * 根據手機號和密碼查詢使用者是否匹配
     * @param mobile
     * @param password
     * @return
     */
    public User findUserByMobileAndPassword(String mobile, String password){
        //根據手機號查詢使用者
        User user = userDao.findByMobile(mobile);
        if(user != null && encoder.matches(password, user.getPassword())){
            return user;
        }else{
            return null;
        }
    }

UserController.java

	/**
	 * 使用者登入的方法
	 * @param loginMap
	 * @return
	 */
	@RequestMapping(value = "/login", method = RequestMethod.POST)
	public Result login(@RequestBody Map<String, String> loginMap){
		User user = userService.findUserByMobileAndPassword(loginMap.get("mobile"), loginMap.get("password"));
		if(user != null){
			return new Result(true, StatusCode.OK, "登入成功");
		}else{
			return new Result(false, StatusCode.LOGINERROR, "使用者名稱或者密碼錯誤");
		}
	}

二、常見的認證機制

1、HTTP Basic Auth

        HTTP Basic Auth簡單點說明就是每次請求API時都提供使用者的username和password。簡言之,Basic Auth是配合RESTful API 使用的最簡單的認證方式,只需提供使用者名稱密碼即可,但由於有把使用者名稱密碼暴露給第三方客戶端的風險,在生產環境下被使用的越來越少。因此,在開發對外開放的RESTfulAPI時,儘量避免採用HTTP Basic Auth

2、Cookie Auth

        Cookie認證機制就是為一次請求認證在服務端建立一個Session物件,同時在客戶端的瀏覽器端建立了一個Cookie物件;通過客戶端帶上來Cookie物件來與伺服器端的session物件匹配來實現狀態管理的。預設的,當我們關閉瀏覽器的時候,cookie會被刪除。但可以通過修改cookie 的expire time使cookie在一定時間內有效;


3、OAuth

        OAuth(開放授權)是一個開放的授權標準,允許使用者讓第三方應用訪問該使用者在某一web服務上儲存的私密的資源(如照片,視訊,聯絡人列表),而無需將使用者名稱和密碼提供給第三方應用。


        OAuth允許使用者提供一個令牌,而不是使用者名稱和密碼來訪問他們存放在特定服務提供者的資料。每一個令牌授權一個特定的第三方系統(例如,視訊編輯網站)在特定的時段(例如,接下來的2小時內)內訪問特定的資源(例如僅僅是某一相簿中的視訊)。這樣,OAuth讓使用者可以授權第三方網站訪問他們儲存在另外服務提供者的某些特定資訊,而非所有內容下面是OAuth2.0的流程:


        這種基於OAuth的認證機制適用於個人消費者類的網際網路產品,如社交類APP等應用,但是不太適合擁有自有認證許可權管理的企業應用。

4、Token Auth

(1)、Token Auth流程

使用基於 Token 的身份驗證方法,在服務端不需要儲存使用者的登入記錄。大概的流程是這樣的:


        ▶ 客戶端使用使用者名稱跟密碼請求登入


        ▶ 服務端收到請求,去驗證使用者名稱與密碼


        ▶ 驗證成功後,服務端會簽發一個 Token,再把這個 Token 傳送給客戶端


        ▶ 客戶端收到 Token 以後可以把它儲存起來,比如放在 Cookie 裡


        ▶ 客戶端每次向服務端請求資源的時候需要帶著服務端簽發的 Token


        ▶ 服務端收到請求,然後去驗證客戶端請求裡面帶著的 Token,如果驗證成功,就向客戶端返回請求的資料


(2)、Token Auth的優點

        支援跨域訪問: Cookie是不允許垮域訪問的,這一點對Token機制是不存在的,前提是傳輸的使用者認證資訊通過HTTP頭傳輸。


        無狀態(也稱:服務端可擴充套件行):Token機制在服務端不需要儲存session資訊,因為Token 自身包含了所有登入使用者的資訊,只需要在客戶端的cookie或本地介質儲存狀態資訊。


        更適用CDN: 可以通過內容分發網路請求你服務端的所有資料(如:javascript,HTML,圖片等),而你的服務端只要提供API即可。


        去耦: 不需要繫結到一個特定的身份驗證方案。Token可以在任何地方生成,只要在你的API被呼叫的時候,你可以進行Token生成呼叫即可。


        更適用於移動應用: 當你的客戶端是一個原生平臺(iOS, Android,Windows 8等)時,Cookie是不被支援的(你需要通過Cookie容器進行處理),這時採用Token認證機制就會簡單得多。


        CSRF:因為不再依賴於Cookie,所以你就不需要考慮對CSRF(跨站請求偽造)的防範。


        效能: 一次網路往返時間(通過資料庫查詢session資訊)總比做一次HMACSHA256計算 的Token驗證和解析要費時得多。


        不需要為登入頁面做特殊處理: 如果你使用Protractor 做功能測試的時候,不再需要為登入頁面做特殊處理。


        基於標準化:你的API可以採用標準化的 JSON Web Token (JWT). 這個標準已經存在多個後端庫(.NET, Ruby, Java,Python, PHP)和多家公司的支援(如:Firebase,Google, Microsoft)。

三、基於JWT的Token認證機制實現

1、什麼是JWT

JSON Web Token(JWT)是一個非常輕巧的規範。這個規範允許我們使用JWT在使用者和伺服器之間傳遞安全可靠的資訊。

2、JWT的組成

一個JWT實際上就是一個字串,它由三部分組成,頭部、載荷與簽名。

3、

四、Java的JJWT實現JWT

五、專案中加入鑑權功能