springboot後臺實現token驗證,使用JWT、shiro的使用

JWT簡單說明:

JWT由單短信息構成:

header(base64加密得到第一部分)、playload(base64加密得到第二部分)、signature(將前兩部分加密的字符串用 . 號拼接u 拼接,再yong hs256加祕加鹽,然後再base64加密,得到第三部分);最後將三部分用 .號連接。

**驗證流程:

用戶驗證成功後會返回給瀏覽器客戶端一個token字符串,服務端不做保存,下次再請求的時候,會攜帶token;

超時驗證-》token合法性驗證(前兩部分加密對比)-〉簽名驗證

token只保存在前端,後端只負責校驗,不可一修改token,只要一改就認證失敗,token簽發後,不能手動使其失效,

如果服務器重啓,token將失效,需要重新登錄。

 

vue的代碼,設置請求頭攜帶toke傳遞給後臺;
 

//設置請求攔截器,把token傳遞到後臺
axios.interceptors.request.use(function(config){
	// console.log(config.url)
	//打開進度條
	// NProgress.start()
	config.headers.Authorization=window.sessionStorage.getItem('token');
	return config;
},function(err){
 
})

springboot後臺代碼:

pom.xml文件中導入依賴:

   <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>
創建簽名和驗證簽名的工具類:

@Data
//@TableName實體類的類名和數據庫表名不一致
//@TableId實體類的主鍵名稱和表中主鍵名稱不一致,主鍵策略type
//@TableField實體類中的成員名稱和表中字段名稱不一致,value是數據庫字段名,fill是自動插入、更新等
@TableName("User")
public class User {
	//主鍵策略
	@TableId(type=IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String token;

@TableField(value="create_time",fill = FieldFill.INSERT)
private Date createTime;
@TableField(value="update_time",fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

}



import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.entity.User;

public class TokenUtil {
	 private static final long EXPIRE_TIME= 15*60*1000;
	    private static final String TOKEN_SECRET="token123";  //密鑰鹽


	    /**
	     * 簽名生成
	     * @param user
	     * @return
	     */
	    public static String sign(User user){

	        String token = null;
	        try {
	            Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME);
	            token = JWT.create()
	                    .withIssuer("auth0")
	                    .withClaim("username", user.getName())
	                    .withExpiresAt(expiresAt)
	                    // 使用了HMAC256加密算法。
	                    .sign(Algorithm.HMAC256(TOKEN_SECRET));
	        } catch (Exception e){
	            e.printStackTrace();
	        }
	        return token;

	    }


	    /**
	     * 簽名驗證
	     * @param token
	     * @return
	     */
	    public static boolean verify(String token){


	        try {
	            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build();
	            DecodedJWT jwt = verifier.verify(token);
	            System.out.println("認證通過:");
	            System.out.println("issuer: " + jwt.getIssuer());
	            System.out.println("username: " + jwt.getClaim("username").asString());
	            System.out.println("過期時間:      " + jwt.getExpiresAt());
	            return true;
	        } catch (Exception e){
	            return false;
	        }

	    }
}
攔截器驗證token:

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import com.example.utils.TokenUtil;

import net.sf.json.JSONObject;
//攔截器
@Component
public class TokenInterceptor implements HandlerInterceptor{
	 @Override
	    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler)throws Exception{

	        if(request.getMethod().equals("OPTIONS")){
	            response.setStatus(HttpServletResponse.SC_OK);
	            return true;
	        }

	        response.setCharacterEncoding("utf-8");

	        String token = request.getHeader("Authorization");
	    System.out.println("token是"+token);
	        if(token != null){
	            boolean result = TokenUtil.verify(token);//驗證token
	            if(result){
	                System.out.println("通過攔截器");
	                return true;
	            }
	        }
	        response.setCharacterEncoding("UTF-8");
	        response.setContentType("application/json; charset=utf-8");
	        PrintWriter out = null;
	        try{
	            JSONObject json = new JSONObject();
	            json.put("success","false");
	            json.put("msg","認證失敗,未通過攔截器");
	            json.put("code","50000");
	            response.getWriter().append(json.toString());
	            System.out.println("認證失敗,未通過攔截器");
	                    response.getWriter().write("50000");
	        }catch (Exception e){
	            e.printStackTrace();
	            response.sendError(500);
	            return false;
	        }


	        return false;

	    }
}
攔截器配置類:

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.example.handler.TokenInterceptor;

/**
 * 攔截器配置
 */
@Configuration
public class IntercepterConfig implements WebMvcConfigurer{
	  private TokenInterceptor tokenInterceptor;

	    //構造方法
	    public IntercepterConfig(TokenInterceptor tokenInterceptor){
	        this.tokenInterceptor = tokenInterceptor;
	    }

	    @Override
	    public void addInterceptors(InterceptorRegistry registry){
	        List<String> excludePath = new ArrayList<>();
	        excludePath.add("/user_register"); //註冊
	        excludePath.add("/login"); //登錄
	        excludePath.add("/logout"); //登出
	        excludePath.add("/static/**");  //靜態資源
	        excludePath.add("/assets/**");  //靜態資源

	        registry.addInterceptor(tokenInterceptor)
	                .addPathPatterns("/**")
	                .excludePathPatterns(excludePath);
	        WebMvcConfigurer.super.addInterceptors(registry);

	    }
}
登錄成功後創建token傳遞給前端:

@RequestMapping("/login")
//	@ResponseBody
	public R login(@RequestBody Map<String, String> person) {	
		 
		User user=new User();
		
		if(person.get("username").equals("lambo")) {
			user.setAge(18);
			user.setName("lmabo");
			user.setId(11L);
			//登錄成功後創建token傳遞給客戶端
			String token = TokenUtil.sign(user);
			user.setToken(token);
			System.out.println("username is:"+person.get("username"));
			return R.ok().data("myinfo",user);
		}else {
			System.out.println("錯誤is:"+person.get("username"));
			return R.error();
		}
	}

 

 

****shiro

https://www.w3cschool.cn/shiro/co4m1if2.html

shiro.apache.org/

http://shiro.apache.org/10-minute-tutorial.html

提供了認證、授權、加密、會話管理,與spring Security 一樣都是做一個權限的安全框架;記住一點,Shiro不會去維護用戶、維護權限;這些需要我們自己去設計/提供;然後通過相應的接口注入給Shiro即可。

認證簡單的說,就是登錄的時候判斷你的用戶名和密碼是否完全匹配,就是證明你是你。

授權,是在認證的基礎之上,進行角色和權限的授予。權限決定了一個用戶可以進行怎樣的操作。

角色、權限

權限定義了一個用戶是否可以執行某個操作。

角色就是一組權限的集合。

1. Subject:主體,一般指用戶。

2. SecurityManager:安全管理器,管理所有Subject,可以配合內部安全組件。(類似於SpringMVC中的DispatcherServlet)

3. Realms:用於進行權限信息的驗證,一般需要自己實現。

詳細功能:

1. Authentication:身份認證/登錄(賬號密碼驗證)。

2. Authorization:授權,即角色或者權限驗證。

3. Session Manager:會話管理,用戶登錄後的session相關管理。

4. Cryptography:加密,密碼加密等。

5. Web Support:Web支持,集成Web環境。

6. Caching:緩存,用戶信息、角色、權限等緩存到如redis等緩存中。

7. Concurrency:多線程併發驗證,在一個線程中開啓另一個線程,可以把權限自動傳播過去。

8. Testing:測試支持;

9. Run As:允許一個用戶假裝爲另一個用戶(如果他們允許)的身份進行訪問。

10. Remember Me:記住我,登錄後,下次再來的話不用登錄了。

 

使用步驟:
1.導入jar包:

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>

2.shiro配置類,當中配置攔截器,決定哪些url需要驗證,哪些角色需要驗證

import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
	 //將自己的驗證方式加入容器
    @Bean
    public CustomRealm myShiroRealm() {
        CustomRealm customRealm = new CustomRealm();
        return customRealm;
    }

    //權限管理,配置主要是Realm的管理認證
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    //Filter工廠,設置對應的過濾條件和跳轉條件,攔截需要安全控制的URL,然後進行相應的控制,其中authc指定需要認證的uri,anon指定排除認證的uri
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> map = new HashMap<>();
        //登出
        map.put("/logout", "logout");
        //對所有用戶認證
        map.put("/**", "authc");
        //不認證的url
        map.put("/login", "anon");
 

3.自定義realm

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import com.example.entity.User;

public class CustomRealm  extends AuthorizingRealm{
	 //數據庫存儲的用戶密碼的加密salt,正式環境不能放在源代碼裏
    private static final String encryptSalt = "jwt";
	
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		
		//加這一步的目的是在Post請求的時候會先進認證,然後在到請求
        if (token.getPrincipal() == null) {
        	
            return null;
        }
        //獲取用戶信息,token中的sername
        String username = token.getPrincipal().toString();
       
        //根據token中username到數據庫中查詢加密過的密碼
        String encrypassword="";
        //獲取數據庫中加密的鹽
        
        User user = new User();
        if (user == null) {
            //這裏返回後會報出對應異常
        	
            return null;
        } else {
            //這裏驗證authenticationToken和simpleAuthenticationInfo的信息
        	//第一個參數:token中的username,第二個參數:數據庫中加密的密碼,第三個參數:加密的鹽,第四個參數:reaml對象,
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, encrypassword, ByteSource.Util.bytes(encryptSalt), getName());
            return simpleAuthenticationInfo;
        }
     
	}
	
	   
    /**
     * 設置自定義認證加密方式
     *
     * @param credentialsMatcher 默認加密方式
     */
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
    		//自定義認證加密方式
        CustomCredentialsMatcher customCredentialsMatcher = new CustomCredentialsMatcher();
        // 設置自定義認證加密方式
        super.setCredentialsMatcher(customCredentialsMatcher);
    }
}

 

 

4.開始配置憑證匹配器,可以用shiro.ini文件,也可以自定義一個匹配器類;

數據庫中的密碼是加密加鹽後的密碼,所以我們給realm配置了加密算法的規則,讓它將我們傳過去的密碼進行了同樣的加密加鹽(這裏鹽不需要我們設置,是從數據庫中查詢出來的),然後再和數據庫的數據進行比對認證.

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;

/**
 * 自定義認證加密方式,這裏的加密方式要和註冊時密碼的加密方式一致
 */
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
    @Override
    public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
      // 獲得用戶輸入的密碼:(可以採用加鹽(salt)的方式去檢驗)
        String inPassword = new String(((UsernamePasswordToken) authcToken).getPassword());
        String username = ((UsernamePasswordToken) authcToken).getUsername();
        //獲得數據庫中的密碼
        String dbPassword = (String) info.getCredentials();
        SimpleAuthenticationInfo saInfo = (SimpleAuthenticationInfo)info;
      //  ByteSource salt = saInfo.getCredentialsSalt();
        ByteSource salt = ByteSource.Util.bytes(username);

        //加密類型,密碼,鹽值,加密次數
        Object tokenCredentials = new SimpleHash("md5", inPassword, username, 2).toHex();
        //數據庫存儲密碼
        Object accountCredentials = getCredentials(info);
        //將密碼加密與系統加密後的密碼校驗,內容一致就返回true,不一致就返回false
        return equals(tokenCredentials, accountCredentials);
    }
}

 

5.在登錄的控制器中:

//帶報錯的數據
    @RequiresRoles("client")
	@RequestMapping("/login")
//	@ResponseBody
	public R login(@RequestBody Map<String, String> person) {	
    	Subject subject = SecurityUtils.getSubject();
        try {
            //將用戶請求參數封裝後,直接提交給Shiro處理
            UsernamePasswordToken token = new UsernamePasswordToken(person.get("username"), person.get("password"));
            subject.login(token);
            System.out.println("認證成功");
        } catch (UnknownAccountException uae) {
        	System.out.println("賬戶不存在");
        } catch (IncorrectCredentialsException ice) {
        	System.out.println("密碼不正確");
        } catch (LockedAccountException lae) {
        	System.out.println("用戶被鎖定了 ");
        } catch (AuthenticationException ae) {
            //無法判斷是什麼錯了
        	System.out.println("未知錯誤");
        }

		return R.ok().data("myinfo",1);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章