主頁 >  其他 > 【Gorho】springboot整合Shiro+jwt 前后端分離 超級詳細的shiro+jwt鑒權程序

【Gorho】springboot整合Shiro+jwt 前后端分離 超級詳細的shiro+jwt鑒權程序

2021-04-22 10:22:13 其他

shiro+jwt+springboot

  • 說在前面
    • 簡介
    • 專案環境(pom.xml)
    • 專案結構(各種包和類)
    • 鑒權流程
  • 具體代碼
    • 配置Shiro
    • 配置JWTUtils
    • 定義JwtFilter
    • 定義JwtToken
    • 定義兩個Realm
    • 兩個"工具人"
        • **JwtCredentialsMatcher.java**
        • **MultiRealmAuthenticator.java**
    • 全域例外處理
    • 兩個controller
        • LoginController.java
        • ArticleController.java
    • 介面測驗

說在前面

簡介

最近粗略地了解了一下shiro框架是怎么使用的,但是視頻講解的是前后端不分離的,雖然思路差不多,但還是好難受,而且沒有講解shiro怎么整合jwt,于是我學習了兩天(四處看博客),終于找到一個良心教程,然后大概把demo給整出來了,就是springboot中整合shiro+jwt,實作token登錄然后shiro保證安全什么的,希望能給過渡期的朋友們一點幫助(找不到好的文章是真的很難受的!!!!)具體的一些解釋大家可以康康代碼里面的注釋噢!還有別導錯包了!

專案環境(pom.xml)

jdk 14 + springboot + shiro1.5.3 + jwt3.3.0 +一些依賴
為了方便大家上手,這里沒有用資料庫,不然又一堆環境什么的問題跑不起來


```java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com</groupId>
    <artifactId>szu</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>szu</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--熱部署插件和lombok插件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <!--引入jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.3.0</version>
        </dependency>
        <!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.5.3</version>
        </dependency>

        <!--commons-lang-->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

專案結構(各種包和類)

相關的類和包包我用藍色框框起來的,大家注意別懵了
在這里插入圖片描述

鑒權流程

我大致畫了一個流程幫助大家理解登錄流程
在這里插入圖片描述

具體代碼

配置Shiro

1、我們這個ShiroConfig禁用了session
2、添加自定義的jwtFilter過濾器,用來攔截自定義的JWT token
3、使用自定義的MultiRealmAuthenticatoe多Realm認證器,解決認證例外無法正常回傳的問題(這我不太懂,抱歉)
4、JwtRealm+ShiroRealm的雙realm,JwtRealm專門處理 JWT token驗證身份的請求

shiroConfig主要是配置shiro的一些基本策略,讓shiro能被用起來,例如我們最基本的需要 shiroFilter、SecurityManager、自定義realm

`package com.szu.shiro.config;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.Filter;

import com.szu.db.ShiroRealm;
import com.szu.filter.JwtFilter;
import com.szu.shiro.realm.JwtRealm;
import com.szu.shiro.realm.MultiRealmAuthenticator;
import com.szu.utils.JwtCredentialsMatcher;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.authz.Authorizer;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.mgt.*;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {
    /**
     * 交由 Spring 來自動地管理 Shiro-Bean 的生命周期
     */
    @Bean
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 為 Spring-Bean 開啟對 Shiro 注解的支持
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator app = new DefaultAdvisorAutoProxyCreator();
        app.setProxyTargetClass(true);
        return app;

    }

    /**
     * 不向 Spring容器中注冊 JwtFilter Bean,防止 Spring 將 JwtFilter 注冊為全域過濾器
     * 全域過濾器會對所有請求進行攔截,而本例中只需要攔截除 /login 和 /logout 外的請求
     * 另一種簡單做法是:直接去掉 jwtFilter()上的 @Bean 注解
     */
    @Bean
    public FilterRegistrationBean<Filter> registration(JwtFilter filter) {
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<Filter>(filter);
        registration.setEnabled(false);
        return registration;
    }

    @Bean
    public JwtFilter jwtFilter() {
        //過濾器如果加了@Compoent就沒必要用這個方法了
        return new JwtFilter();
    }

    /**
     * 配置訪問資源需要的權限
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //給工廠bean設定web安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/authorized");
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        // 添加 jwt 專用過濾器,攔截除 /login 和 /logout 外的請求
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("jwtFilter", jwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

        //配置系統受限資源以及公共資源
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        filterChainDefinitionMap.put("/login", "anon"); // 可匿名訪問
        filterChainDefinitionMap.put("/logout", "logout"); // 退出登錄
        filterChainDefinitionMap.put("/**", "jwtFilter,authc"); // 需登錄才能訪問
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 配置 ModularRealmAuthenticator
     */
    @Bean
    public ModularRealmAuthenticator authenticator() {
        ModularRealmAuthenticator authenticator = new MultiRealmAuthenticator();
        // 設定多 Realm的認證策略,默認 AtLeastOneSuccessfulStrategy
        AuthenticationStrategy strategy = new FirstSuccessfulStrategy();
        authenticator.setAuthenticationStrategy(strategy);
        return authenticator;
    }

    /**
     * 禁用session, 不保存用戶登錄狀態,保證每次請求都重新認證
     */
    @Bean
    protected SessionStorageEvaluator sessionStorageEvaluator() {
        DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        return sessionStorageEvaluator;
    }

    /**
     * JwtRealm 配置,需實作 Realm 介面
     */
    @Bean
    JwtRealm jwtRealm() {
        JwtRealm jwtRealm = new JwtRealm();
        // 設定加密演算法
        CredentialsMatcher credentialsMatcher = new JwtCredentialsMatcher();
        // 設定加密次數
        jwtRealm.setCredentialsMatcher(credentialsMatcher);
        return jwtRealm;
    }

    /**
     * ShiroRealm 配置,需實作 Realm 介面
     */
    @Bean
    ShiroRealm shiroRealm() { //這里其實是模擬的資料庫的類,但是也繼承了AuthorizingRealm
        ShiroRealm shiroRealm = new ShiroRealm();
        // 設定加密演算法
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher("SHA-1");
        // 設定加密次數
        credentialsMatcher.setHashIterations(16);
        shiroRealm.setCredentialsMatcher(credentialsMatcher);
        return shiroRealm;
    }

    /**
     * 配置 DefaultWebSecurityManager
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 1.Authenticator
        securityManager.setAuthenticator(authenticator());

        // 2.Realm
        List<Realm> realms = new ArrayList<Realm>(16);
        realms.add(jwtRealm());
        realms.add(shiroRealm());
        securityManager.setRealms(realms); // 配置多個realm

        // 3.關閉shiro自帶的session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator());
        securityManager.setSubjectDAO(subjectDAO);

        return securityManager;
    }

    @Bean
    public Authorizer authorizer(){
        //這里是個坑,如果沒有這個bean,啟動會報錯,所以得加上
        return new ModularRealmAuthorizer();
    }
}

配置JWTUtils

jwtutils作用主要是:
定義token過期時間,創建token的密鑰(自定義),自定義存放token的請求頭的名稱
①簽發token
②判斷過期時間
③重繪token

package com.szu.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;

@Component
public class JwtUtils {
    // 過期時間5分鐘
    private static final long EXPIRE_TIME = 5*60*1000;

    //自己定制密鑰
    public static final String SECRET = "SECRET_VALUE";

    //請求頭
    public static final String AUTH_HEADER = "X-Authorization-With";

    /**
     * 驗證token是否正確
     * @param token
     * @param username
     * @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 (JWTVerificationException exception){
            return false;
        } catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 獲得token中的自定義資訊,一般是獲取token的username,無需secret解密也能獲得
     * @param token
     * @param filed
     * @return
     */
    public static String getClaimFiled(String token, String filed){
        try{
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(filed).asString();
        } catch (JWTDecodeException e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 生成簽名,準確地說是生成token
     * @param username
     * @param secret
     * @return
     */
    public static String sign(String username, String secret){
        try{
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            //附帶username,nickname資訊
            return JWT.create().withClaim("username",username).withExpiresAt(date).sign(algorithm);
        } catch (JWTCreationException e){
            e.printStackTrace();
            return null;
        } catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 獲取token的簽發時間
     * @param token
     * @return
     */
    public static Date getIssueAt(String token){
        try{
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getIssuedAt();
        } catch (JWTDecodeException e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 驗證token是否過期
     * @param token
     * @return
     */
    public static boolean isTokenExpired(String token){
        Date now = Calendar.getInstance().getTime();
        DecodedJWT jwt = JWT.decode(token);
        return jwt.getExpiresAt().before(now);
    }

    /**
     * 重繪token的有效期
     * @param token
     * @param secret
     * @return
     */
    public static String refreshTokenExpired(String token, String secret){
        DecodedJWT jwt = JWT.decode(token); //決議token
        Map<String, Claim> claims = jwt.getClaims(); //獲取token的引數資訊

        try{
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTCreator.Builder builder = JWT.create().withExpiresAt(date);
            for(Map.Entry<String,Claim> entry : claims.entrySet()){
                builder.withClaim(entry.getKey(),entry.getValue().asString());
            }
            return builder.sign(algorithm);
        } catch (JWTCreationException | UnsupportedEncodingException e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 生成16位隨機鹽,用在密碼加密上面
     * @return
     */
    public static String generateSalt(){
        SecureRandomNumberGenerator secureRandomNumberGenerator = new SecureRandomNumberGenerator();
        String hex = secureRandomNumberGenerator.nextBytes(16).toHex();
        return hex;
    }
}

定義JwtFilter

jwtfilter的作用故名思義,就是一個專門用來攔截含有token請求的過濾器,
對于前端發來的請求,這個過濾器都會進行過濾,具體是咱們的前置攔截處理和后置攔截處理分別回傳回應正常狀態和添加跨域請求,
除此以外,我們的isAccessAllowed()方法會驗證token的正確性,正確則繼續往下請求,如果驗證出錯則回傳錯誤資訊,拒絕訪問!【具體的流程大家可以康康代碼,我都有寫注釋的噢,花點時間的話還算好理解,講的話太長了,而我明天還有課啊啊啊】

package com.szu.filter;

import com.szu.shiro.token.JwtToken;
import com.szu.utils.JwtUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * 自定義的認證過濾器,用來攔截Header中攜帶token的請求
 */
@Component
public class JwtFilter extends BasicHttpAuthenticationFilter {

    /**
     * 前置攔截處理
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        //servlet請求與回應的轉換
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);

        //跨域時會首先發送一個option請求,這里我們給option請求直接回傳正常狀態
        if(httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())){
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 后置攔截處理
     * @param request
     * @param response
     * @throws Exception
     */
    @Override
    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
        //添加跨域支持
        this.fillCorsHeader(WebUtils.toHttp(request),WebUtils.toHttp(response));
    }

    /**
     * 過濾器攔截請求的入口方法,所有請求都會進入該方法
     * 回傳true則允許訪問
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        // 原用來判斷是否是登錄請求,在本例中不會攔截登錄請求,用來檢測Header是否包含token欄位
        if(this.isLoginRequest(request,response)){//看看原始碼,呼叫了isLoginAttempt()方法
            return false; //回傳false后進入onAccessDenied()方法,回傳錯誤資訊
        }

        boolean allowed = false;
        try{
            //檢測header里的JWT Token內容是否正確,嘗試使用token進行登錄
            allowed = this.executeLogin(request,response);
        } catch (IllegalStateException e){ //未找到token
            e.printStackTrace();
            System.out.println("未找到token");
        } catch (Exception e){
            e.printStackTrace();
            System.out.println("token檢驗出錯");
        }
        return allowed || super.isPermissive(mappedValue);
    }

    /**
     * 檢測Header中是否包含token欄位
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        return ((HttpServletRequest) request).getHeader(JwtUtils.AUTH_HEADER) == null;
    }

    /**
     * 身份驗證,檢查JWT token是否合法
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        //從請求頭里拿到token
        AuthenticationToken token = createToken(request,response);
        if(token == null){
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        try{
            Subject subject = getSubject(request,response);
            subject.login(token); //讓shiro進行登錄驗證
            //沒出錯則驗證成功
            return onLoginSuccess(token,subject,request,response);
        } catch (AuthenticationException e){
            return onLoginFailure(token, e, request, response);
        }
    }

    /**
     * 從Header中提取 JWT token
     * @param request
     * @param response
     * @return
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String authorization = httpServletRequest.getHeader(JwtUtils.AUTH_HEADER);
        JwtToken token = new JwtToken(authorization);
        return token;
    }

    /**
     * isAccessAllowed()方法回傳false,會進入該方法,表示拒絕訪問
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());

        PrintWriter writer = httpServletResponse.getWriter();
        writer.write("{\"errorCode\":401,\"msg\":\"UNAUTHORIZED\"}");
        fillCorsHeader(WebUtils.toHttp(request),httpServletResponse);
        return false;
    }

    /**
     * shiro利用 JWT Token 登錄成功后,進入該方法
     * @param token
     * @param subject
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        String newToken = null;

        //登錄成功后重繪token
        if(token instanceof JwtToken) {
            newToken = JwtUtils.refreshTokenExpired(token.getCredentials().toString(),JwtUtils.SECRET);
        }
        if(newToken != null){
            httpResponse.setHeader(JwtUtils.AUTH_HEADER,newToken);
        }
        return true;
    }

    /**
     * 利用 JWT token 登錄失敗,會進入該方法
     * @param token
     * @param e
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        return false;//直接回傳false,交給后面的 onAccessDenied()方法處理
    }
	//跨域請求的解決方案之一
    protected void fillCorsHeader(HttpServletRequest request, HttpServletResponse response){
        response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,HEAD");
        response.setHeader("Access-Control-Allow-Headers",
                request.getHeader("Access-Control-Request-Headers"));
    }
}

定義JwtToken

JwtToken這個類實作了AuthenticationToken,如果我們將token作為引數構造出JwtToken物件的話,shiro的主體物件subject進行登錄驗證時就不用再new 一個UsernamePasswordToken 物件了,畢竟 JwtToken和UsernamePasswordToken 都繼承了同一個介面,
所以,說到這JwtToken的作用就顯而易見了,目的就是構造一個AuthenticationToken物件以供shiro登錄驗證

package com.szu.shiro.token;

import com.szu.utils.JwtUtils;
import org.apache.shiro.authc.AuthenticationToken;

/**
 * 該類與UsernamePasswordToken差不多,都是AuthenticationToken介面的實作類
 * 目的是封裝成UsernamePasswordToken讓shiro進行登錄、登出等操作
 */
public class JwtToken implements AuthenticationToken {

    private static final long serialVersionUID = 1L;

    //加密后的 JWT token
    private String token;

    private String username;

    public JwtToken(String token){
        this.token = token;
        this.username = JwtUtils.getClaimFiled(token,"username");
    }

    @Override
    public Object getPrincipal() {
        return this.username;
    }

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

定義兩個Realm

ShiroRealm:其實是模擬的資料庫以及查詢
JwtRealm:這個自定義的realm就比較關鍵了,它實作了認證和授權的兩個方法,
認證的方法里面,我們獲取到JwtToken類的token后,獲取token里面的引數資訊(暫時只有username),然后查詢“資料庫”判斷,沒有則回傳錯誤資訊,即拋出例外,讓subject.login(token)所在的方法捕獲到例外進行處理,認證通過,即用戶名所對應的物件存在,則回傳SimpleAuthenticationInfo物件,讓請求能夠繼續請求loginController
授權的方法中,則是獲取到token攜帶的的username資訊來查詢其擁有的權限,然后進行設定即可,至此,我們的shiro作用就發揮得差不多了
[realm是由shiroConfig中的securityManager呼叫的]

JwtRealm.java

package com.szu.shiro.realm;

import com.szu.db.ShiroRealm;
import com.szu.entity.User;
import com.szu.entity.UserEntity;
import com.szu.mapper.UserMapper;
import com.szu.shiro.token.JwtToken;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Set;

@Component
public class JwtRealm extends AuthorizingRealm {

    /**
     * 限定這個 Realm 只處理我們自定義的 JwtToken
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 此處的 SimpleAuthenticationInfo 可回傳任意值,密碼校驗時不會用到它
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
            throws AuthenticationException {
        JwtToken jwtToken = (JwtToken) authcToken;
        if (jwtToken.getPrincipal() == null) {
            throw new AccountException("JWT token引數例外!");
        }
        // 從 JwtToken 中獲取當前用戶
        String username = jwtToken.getPrincipal().toString();
        // 查詢資料庫獲取用戶資訊,此處使用 Map 來模擬資料庫
        UserEntity user = ShiroRealm.userMap.get(username);

        // 用戶不存在
        if (user == null) {
            throw new UnknownAccountException("用戶不存在!");
        }

        // 用戶被鎖定
        if (user.getLocked()) {
            throw new LockedAccountException("該用戶已被鎖定,暫時無法登錄!");
        }

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, username, getName());
        return info;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 獲取當前用戶
        UserEntity currentUser = (UserEntity) SecurityUtils.getSubject().getPrincipal();
        // UserEntity currentUser = (UserEntity) principals.getPrimaryPrincipal();
        // 查詢資料庫,獲取用戶的角色資訊
        Set<String> roles = ShiroRealm.roleMap.get(currentUser.getName());
        // 查詢資料庫,獲取用戶的權限資訊
        Set<String> perms = ShiroRealm.permMap.get(currentUser.getName());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(roles);
        info.setStringPermissions(perms);
        return info;
    }
}

ShiroRealm.java
此處存盤的密碼都是被shiro加密過的

String password = new SimpleHash("MD5", user.getPassword(), user.getUserName()
package com.szu.db;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.szu.entity.UserEntity;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

/**
 * 同時開啟身份驗證和權限驗證,需要繼承 AuthorizingRealm
 * 并實作其  doGetAuthenticationInfo()和 doGetAuthorizationInfo 兩個方法
 */
@SuppressWarnings("serial")
public class ShiroRealm extends AuthorizingRealm {
    public static Map<String, UserEntity> userMap = new HashMap<String, UserEntity>(16);
    public static Map<String, Set<String>> roleMap = new HashMap<String, Set<String>>(16);
    public static Map<String, Set<String>> permMap = new HashMap<String, Set<String>>(16);

    static {
        UserEntity user1 = new UserEntity(1L, "gorho", "dd524c4c66076d1fa07e1fa1c94a91233772d132", "灰先生", false);
        UserEntity user2 = new UserEntity(2L, "plum", "cce369436bbb9f0325689a3a6d5d6b9b8a3f39a0", "李先生", false);

        userMap.put("gorho", user1);
        userMap.put("plum", user2);

        roleMap.put("gorho", new HashSet<String>() {
            {
                add("admin");

            }
        });

        roleMap.put("plum", new HashSet<String>() {
            {
                add("guest");
            }
        });
        permMap.put("plum", new HashSet<String>() {
            {
                add("article:read");
            }
        });
    }

    /**
     * 限定這個 Realm 只處理 UsernamePasswordToken
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }

    /**
     * 查詢資料庫,將獲取到的用戶安全資料封裝回傳
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 從 AuthenticationToken 中獲取當前用戶
        String username = (String) token.getPrincipal();
        // 查詢資料庫獲取用戶資訊,此處使用 Map 來模擬資料庫
        UserEntity user = userMap.get(username);

        // 用戶不存在
        if (user == null) {
            throw new UnknownAccountException("用戶不存在!");
        }

        // 用戶被鎖定
        if (user.getLocked()) {
            throw new LockedAccountException("該用戶已被鎖定,暫時無法登錄!");
        }

        // 使用用戶名作為鹽值
        ByteSource credentialsSalt = ByteSource.Util.bytes(username);

        /**
         * 將獲取到的用戶資料封裝成 AuthenticationInfo 物件回傳,此處封裝為 SimpleAuthenticationInfo 物件,
         *  引數1. 認證的物體資訊,可以是從資料庫中獲取到的用戶物體類物件或者用戶名
         *  引數2. 查詢獲取到的登錄密碼
         *  引數3. 鹽值
         *  引數4. 當前 Realm 物件的名稱,直接呼叫父類的 getName() 方法即可
         */
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), credentialsSalt,
                getName());
        return info;
    }

    /**
     * 查詢資料庫,將獲取到的用戶的角色及權限資訊回傳
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 獲取當前用戶
        UserEntity currentUser = (UserEntity) SecurityUtils.getSubject().getPrincipal();
        // UserEntity currentUser = (UserEntity)principals.getPrimaryPrincipal();
        // 查詢資料庫,獲取用戶的角色資訊
        Set<String> roles = roleMap.get(currentUser.getName());
        // 查詢資料庫,獲取用戶的權限資訊
        Set<String> perms = permMap.get(currentUser.getName());

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(roles);
        info.setStringPermissions(perms);
        return info;
    }
}

兩個"工具人"

JwtCredentialsMatcher.java

該類的主要作用就是驗證JwtToken的內容是否合法,是realm的憑證校驗器,不自定義也可以的,換成HashedCredentialsMatcher并設定加密方式,散列次數什么的就行了

package com.szu.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.springframework.stereotype.Component;

@Component
public class JwtCredentialsMatcher implements CredentialsMatcher {
    /**
     * JwtCredentialsMatcher只需驗證JwtToken內容是否合法
     */
    @Override
    public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {

        String token = authenticationToken.getCredentials().toString();
        String username = authenticationToken.getPrincipal().toString();
        try {
            Algorithm algorithm = Algorithm.HMAC256(JwtUtils.SECRET);
            JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
            verifier.verify(token);
            return true;
        } catch (JWTVerificationException e) {
            e.printStackTrace();
        } catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }
}

MultiRealmAuthenticator.java

MultiRealmAuthenticator 用來解決Shiro中出現的具體的認證例外無法正常回傳,僅回傳父類 AuthenticationException 的問題,

package com.szu.shiro.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Collection;

@Component
public class MultiRealmAuthenticator extends ModularRealmAuthenticator {

    private static final Logger log = LoggerFactory.getLogger(MultiRealmAuthenticator.class);

    @Override
    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token)
            throws AuthenticationException {
        AuthenticationStrategy strategy = getAuthenticationStrategy();

        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

        if (log.isTraceEnabled()) {
            log.trace("Iterating through {} realms for PAM authentication", realms.size());
        }
        AuthenticationException authenticationException = null;
        for (Realm realm : realms) {

            aggregate = strategy.beforeAttempt(realm, token, aggregate);

            if (realm.supports(token)) {

                log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);

                AuthenticationInfo info = null;
                try {
                    info = realm.getAuthenticationInfo(token);
                } catch (AuthenticationException e) {
                    authenticationException = e;
                    if (log.isDebugEnabled()) {
                        String msg = "Realm [" + realm
                                + "] threw an exception during a multi-realm authentication attempt:";
                        log.debug(msg, e);
                    }
                }

                aggregate = strategy.afterAttempt(realm, token, info, aggregate, authenticationException);

            } else {
                log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
            }
        }
        if (authenticationException != null) {
            throw authenticationException;
        }
        aggregate = strategy.afterAllAttempts(token, aggregate);

        return aggregate;
    }
}

全域例外處理

這個類主要是用來捕獲例外并回傳不同例外的錯誤資訊,方便前端判斷并處理,但是這里只是簡單提了,具體的教程我會以后寫一篇的哈,大家先有這個前后端分離的概念就行

package com.szu.exception;

import com.szu.entity.dto.BaseResponse;
import org.apache.shiro.ShiroException;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;

@RestControllerAdvice
public class MyExceptionHandler {
    // 捕捉shiro的例外
    @ExceptionHandler(ShiroException.class)
    public Object handleShiroException(ShiroException e) {
        BaseResponse<Object> ret = new BaseResponse<Object>();
        ret.setErrCode(401);
        ret.setMsg(e.getMessage());
        return ret;
    }

    // 捕捉其他所有例外
    @ExceptionHandler(Exception.class)
    public Object globalException(HttpServletRequest request, Throwable ex) {
        BaseResponse<Object> ret = new BaseResponse<Object>();
        ret.setErrCode(401);
        ret.setMsg(ex.getMessage());
        return ret;
    }

}

兩個controller

LoginController.java

登錄的控制器,在controller中用shiro進行登錄的驗證判斷
該控制器方法主要想體現身份的權限控制

package com.szu.controller;

import com.szu.entity.dto.BaseResponse;
import com.szu.utils.JwtUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

@RestController
public class LoginController {
    @PostMapping(value = "/login")
    public Object userLogin(@RequestParam(name = "username", required = true) String userName,
                            @RequestParam(name = "password", required = true) String password, ServletResponse response) {

        // 獲取當前用戶主體
        Subject subject = SecurityUtils.getSubject();
        String msg = null;
        boolean loginSuccess = false;
        // 將用戶名和密碼封裝成 UsernamePasswordToken 物件
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        try {
            subject.login(token);
            msg = "登錄成功,";
            loginSuccess = true;
        } catch (UnknownAccountException uae) { // 賬號不存在
            msg = "用戶名與密碼不匹配,請檢查后重新輸入!";
        } catch (IncorrectCredentialsException ice) { // 賬號與密碼不匹配
            msg = "用戶名與密碼不匹配,請檢查后重新輸入!";
        } catch (LockedAccountException lae) { // 賬號已被鎖定
            msg = "該賬戶已被鎖定,如需解鎖請聯系管理員!";
        } catch (AuthenticationException ae) { // 其他身份驗證例外
            msg = "登錄例外,請聯系管理員!";
        }
        BaseResponse<Object> ret = new BaseResponse<Object>();
        if (loginSuccess) {
            // 若登錄成功,簽發 JWT token
            String jwtToken = JwtUtils.sign(userName, JwtUtils.SECRET);
            // 將簽發的 JWT token 設定到 HttpServletResponse 的 Header 中
            ((HttpServletResponse) response).setHeader(JwtUtils.AUTH_HEADER, jwtToken);
            //
            ret.setErrCode(0);
            ret.setMsg(msg);
            return ret;
        } else {
            ret.setErrCode(401);
            ret.setMsg(msg);
            return ret;
        }

    }

    @GetMapping("/logout")
    public Object logout() {
        BaseResponse<Object> ret = new BaseResponse<Object>();
        ret.setErrCode(0);
        ret.setMsg("退出登錄");
        return ret;
    }

}

ArticleController.java

這個控制器類方法可以在用戶登錄后回傳簡單的示意資訊
主要想體現對用戶權限的控制

package com.szu.controller;

import com.szu.entity.dto.BaseResponse;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/article")
public class ArticleController {
    @GetMapping("/delete")
    @RequiresRoles(value = { "admin" })
    public Object deleteArticle(ModelMap model) {
        BaseResponse<Object> ret = new BaseResponse<Object>();
        ret.setErrCode(0);
        ret.setMsg("文章洗掉成功!");
        return ret;
    }

    @GetMapping("/read")
    @RequiresPermissions(value = { "article:read" })
    public Object readArticle(ModelMap model) {
        BaseResponse<Object> ret = new BaseResponse<Object>();
        ret.setErrCode(0);
        ret.setMsg("請您鑒賞!");
        return ret;
    }
}

介面測驗

啟動springboot后,任意選一個你喜歡的介面測驗工具,首先進行登錄測驗
如圖,post請求登錄
在這里插入圖片描述

回傳結果如下:
注意,左側的請求頭里面出現了我們的token資料,說明咱們的代碼沒有白寫(crtl c+v)
在這里插入圖片描述
然后復制我們的token,加到請求頭中,我們就可以在時效內訪問文章啦【電腦要沒電了,剩下的不演示了】
在這里插入圖片描述回傳成功!!!
在這里插入圖片描述
兄弟們,這就是簡單的前后端分離的shiro+jwt的鑒權控制了,代碼只要復制就能跑的,大家不妨嘗試一下!!重要地方都有注釋
如果有用的話記得給文章點贊、收藏、轉發哈,下一篇我們講《前后端分離之全域例外處理并回傳狀態碼》或者 《mybatis-plus的crud》

文章參考自https://blog.csdn.net/pengjunlee/article/details/95600843!良心教程,幫助我成功理解了!!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/278808.html

標籤:其他

上一篇:告別DNS劫持,一文讀懂DoH

下一篇:CSRF:你的身邊安全嗎

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more