天天看點

SpringBoot整合Shiro+Jwt實作登入認證和授權shiro整合JWT總結

shiro

簡介

  1. Subject:主體
    1. 代表了目前 “使用者”,這個使用者不一定是一個具體的人,與目前應用互動的任何東西都是 Subject,如網絡爬蟲,機器人等;即一個抽象概念;所有 Subject 都綁定到 SecurityManager,與 Subject 的所有互動都會委托給 SecurityManager;可以把 Subject 認為是一個門面;
  2. SecurityManager:安全管理器
    1. 即所有與安全有關的操作都會與 SecurityManager 互動;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它負責與後邊介紹的其他元件進行互動,如果學習過 SpringMVC,你可以把它看成 DispatcherServlet 前端控制。
  3. Realms:域
    1. Shiro 從從 Realm 擷取安全資料(如使用者、角色、權限),就是說 SecurityManager 要驗證使用者身份,那麼它需要從 Realm 擷取相應的使用者進行比較以确定使用者身份是否合法;也需要從 Realm 得到使用者相應的角色 / 權限進行驗證使用者是否能進行操作;可以把 Realm 看成 DataSource,即安全資料源。

登入認證代碼實作

pom

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.2</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.3.2</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
        <version>1.18.12</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.20</version>
    </dependency>

    <!-- shiro -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>${shiro-version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>${shiro-version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-web</artifactId>
        <version>${shiro-version}</version>
    </dependency>


    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>
           

實體

使用者實體

@Data
@TableName("sys_user")
public class SysUser {

    private int id;
    private String username;
    private String password;
    private int status;
    private int isDelete;

}
           

使用者資料庫

id username password status is_delete
1 admin 123
2 aaa 82c1a1ef7dd57d095f3d221e51bd6b16

自定義realm

public class MyRealm extends AuthorizingRealm {

    @Autowired
    private SysUserService userService;

    /**
     * 授權
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
     * 登入認證
     * @param authenticationToken 封裝的token(UsernamePasswordToken)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        if (authenticationToken.getPrincipal() == null) {
            throw new AuthenticationException("token不合法");
        }
        String username = authenticationToken.getPrincipal().toString();
        log.info("使用者名:{}", username);
        SysUser one = userService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
        if (one == null) {
            // 沒有該使用者名
            log.error("沒有該使用者名: {}", username);
            throw new AuthenticationException("使用者不存在");
        }
        // 使用者狀态判斷
        if (one.getStatus() == 1) {
            throw new AccountException("使用者被禁用");
        }
        if (one.getIsDelete() == 1) {
            throw new AccountException("使用者被删除");
        }
        // 其他業務判斷
        // 判斷通過後,将資料庫中查詢出來的user封裝為info
        return new SimpleAuthenticationInfo(
                one.getUsername(),// 這個參數是什麼,在後續的subject.getPrincipal就是什麼,也可以設定使用者實體
                one.getPassword(),
//                ByteSource.Util.bytes(one.getUsername()),// 密碼加密的"鹽值",可以是username、id等
                getName()
        );
    }
}
           

shiro配置類

public class ShiroConfig {

    @Bean("credentialsMatcher")
    public HashedCredentialsMatcher credentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 密碼加密算法
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        // 加密次數
        hashedCredentialsMatcher.setHashIterations(512);
        return hashedCredentialsMatcher;
    }

    /**
     * 自定義realm
     */
    @Bean("myRealm")
    public MyRealm myRealm(@Qualifier("credentialsMatcher") HashedCredentialsMatcher credentialMatcher) {
        MyRealm myRealm = new MyRealm();
//        myRealm.setCredentialsMatcher(credentialMatcher);
        return myRealm;
    }

    @Bean("securityManager")
    public SecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm);
        /*
         * 關閉shiro自帶的session,詳情見文檔
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        manager.setSubjectDAO(subjectDAO);
        return manager;
    }

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
        filter.setSecurityManager(securityManager);
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 路徑比對的順序就是put進去的順序(最先比對原則)
        // login接口不需要認證
        filterChainDefinitionMap.put("/auth/login", "anon");
        // getInfo需要認證
        filterChainDefinitionMap.put("/auth/getInfo", "authc");
		filterChainDefinitionMap.put("/**", "authc");
        
        filter.setLoginUrl("/auth/login");
        filter.setSuccessUrl("/auth/getInfo");
        filter.setUnauthorizedUrl("/auth/error");
        filter.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return filter;
    }

    /**注冊shiro的Filter 攔截請求*/
    @Bean
    public FilterRegistrationBean<Filter> filterRegistrationBean(SecurityManager securityManager) throws Exception {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter((Filter) Objects.requireNonNull(this.shiroFilterFactoryBean(securityManager).getObject()));
        filterRegistrationBean.addInitParameter("targetFilterLifecycle","true");
        //bean注入開啟異步方式
        filterRegistrationBean.setAsyncSupported(true);
        filterRegistrationBean.setEnabled(true);
        filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST);
        return filterRegistrationBean;
    }


    /**
     * shiro聲明周期
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    // 以下配置開啟shiro注解(@RequiresPermissions)

    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 強制使用cglib,防止重複代理和可能引起代理出錯的問題
        // https://zhuanlan.zhihu.com/p/29161098
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 啟用shiro注解
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}
           

登入業務

public class AuthServiceImpl implements AuthService {

    @Override
    public String login(SysUser sysUser) {
        // 非空判斷
        if (sysUser == null) {
            return null;
        }
        if (sysUser.getUsername() == null || sysUser.getPassword() == null) {
            return null;
        }
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                sysUser.getUsername(),
                sysUser.getPassword()
        );
        try {
            subject.login(usernamePasswordToken);
        } catch (AuthenticationException e) {
            log.error("登入失敗");
            throw new AuthenticationException("登入失敗");
        }
        return "登入成功";
    }
}
           

接口

@RestController
@RequestMapping("auth")
public class AuthController {
    @Autowired
    private AuthService authService;

    @PostMapping("login")
    public String login(@RequestBody SysUser sysUser) {
        return authService.login(sysUser);
    }

    @GetMapping("getInfo")
    public String getInfo() {
        return SecurityUtils.getSubject().getPrincipal().toString();
    }

    @RequestMapping("error")
    public String error() {
        return "fail";
    }
}
           

測試

SpringBoot整合Shiro+Jwt實作登入認證和授權shiro整合JWT總結

當直接通路

auth/getInfo

時,可以看到,無法通路該接口,并且會自動跳轉到登入接口(

auth/login

)。

SpringBoot整合Shiro+Jwt實作登入認證和授權shiro整合JWT總結

使用使用者名和密碼登入

然後再通路

auth/getInfo

接口

SpringBoot整合Shiro+Jwt實作登入認證和授權shiro整合JWT總結

現在可以正确擷取到資訊,可以看到擷取到的

SecurityUtils.getSubject().getPrincipal()

,就是在reamlm中傳回的info設定的

principal

參數。

注:如果要使用加密,則要在shiro配置類裡,給自定義的realm設定密碼比對器

@Bean("credentialsMatcher")
public HashedCredentialsMatcher credentialsMatcher() {
    HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    // 密碼加密算法
    hashedCredentialsMatcher.setHashAlgorithmName("MD5");
    // 加密次數
    hashedCredentialsMatcher.setHashIterations(512);
    return hashedCredentialsMatcher;
}

/**
     * 自定義realm
     */
@Bean("myRealm")
public MyRealm myRealm(@Qualifier("credentialsMatcher") HashedCredentialsMatcher credentialMatcher) {
    MyRealm myRealm = new MyRealm();
    myRealm.setCredentialsMatcher(credentialMatcher);
    return myRealm;
}
           

自定義realme裡面最後傳回的info需要帶上加密的“鹽值”

return new SimpleAuthenticationInfo(
    one.getUsername(),
    one.getPassword(),
    ByteSource.Util.bytes(one.getUsername()),
    getName()
);
           

總結

登入認證的流程:

  1. 将前端傳來的使用者名和密碼封裝成

    UsernamePasswordToken

  2. 調用Subject的

    login(UsernamePasswordToken)

    方法,實際上是調用的

    SecurityManager

    的login方法
  3. 進入自定義realme方法
  4. 最終由定義的密碼比對器進行密碼比對

SimpleCredentialsMatcher源碼:預設使用該比對器,即不加密

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    Object tokenCredentials = this.getCredentials(token);
    Object accountCredentials = this.getCredentials(info);
    return this.equals(tokenCredentials, accountCredentials);
}
// token:前端封裝的使用者名密碼token
// info:realme中傳回的info
           

實際就是把資料庫中的密碼和前端輸入的密碼進行對比

HashedCredentialsMatcher源碼:加密的密碼比對器

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    Object tokenHashedCredentials = this.hashProvidedCredentials(token, info);
    Object accountCredentials = this.getCredentials(info);
    return this.equals(tokenHashedCredentials, accountCredentials);
}

protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
    Object salt = null;
    if (info instanceof SaltedAuthenticationInfo) {
        salt = ((SaltedAuthenticationInfo)info).getCredentialsSalt();
    } else if (this.isHashSalted()) {
        salt = this.getSalt(token);
    }

    return this.hashProvidedCredentials(token.getCredentials(), salt, this.getHashIterations());
}

// 在這裡實作的加密
protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) {
    String hashAlgorithmName = this.assertHashAlgorithmName();
    return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
}
           

把前端輸入的明文密碼,用設定的鹽值和加密次數進行加密後,再與資料庫中的密文密碼進行對比。

授權代碼實作

授權就需要實作自定義realme中的

doGetAuthorizationInfo

方法

資料庫

sys_role

id name code staus Is_delete
1 管理者 admin
2 作家 writer

sys_permission

id name code url status Is_delete
1 使用者檢視 user:view /user/**
2 使用者增删改 user:edit /user/**
3 文章檢視 article:view /article/**
4 文章增删改 article:edit /article/**

另外還有使用者角色關聯表和角色權限關聯表

我這裡測試的資料是:

admin使用者是管理者和作家,aaa使用者是作家。

管理者擁有所有權限,作家擁有文章相關的權限。

修改自定義realme

/**
     * 授權
     */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    String username = principalCollection.getPrimaryPrincipal().toString();
    SysUser sysUser = userService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
    // 去資料庫中查詢該使用者的角色和權限
    List<SysRole> roles = roleService.getRoles(sysUser.getId());
    List<SysPermission> permissions = permissionService.getPermissions(sysUser.getId());

    Set<String> permissionSet = new HashSet<>();
    Set<String> roleSet = new HashSet<>();

    permissions.forEach(item -> permissionSet.add(item.getCode()));
    roles.forEach(item -> roleSet.add(item.getCode()));

    authorizationInfo.addRoles(roleSet);
    authorizationInfo.addStringPermissions(permissionSet);
    return authorizationInfo;
}
           

當遇到需要鑒權的時候,會走

doGetAuthorizationInfo

方法

測試

接口

@RequiresPermissions("user:view")
@GetMapping("userView")
public String userView() {
    return "使用者檢視";
}

@RequiresPermissions("article:view")
@GetMapping("articleView")
public String articleView() {
    return "文章檢視";
}

@RequiresRoles("admin")
@GetMapping("admin")
public String admin() {
    return "管理者";
}
           

登入使用者是

aaa

的時候,隻能通路

articleView

接口,其他接口均無相應的權限。

SpringBoot整合Shiro+Jwt實作登入認證和授權shiro整合JWT總結

通路articleView接口,可以成功通路

當通路其他兩個接口時,控制台報錯無權調用此方法

org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method

整合JWT

準備工作

pom

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>
           

jwt工具類

建立、解析token

@Slf4j
@Component
public class JwtUtil {

    private static final String secret = "secret";

    /**
     * 建立token
     */
    public static String createToken(String username, Long time) throws UnsupportedEncodingException {
        long expiration = System.currentTimeMillis() + time;
        Date expireDate = new Date(expiration);
        String token = JWT.create()
                .withClaim("sys_username", username)
                .withExpiresAt(expireDate)
                .sign(Algorithm.HMAC256(secret));
        log.info("使用者:{} =====> token:{}", username, token);
        return token;
    }

    /**
     * 校驗token是否正确
     */
    public static boolean verify(String token, String username) throws UnsupportedEncodingException, TokenExpiredException {
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTVerifier verifier = JWT.require(algorithm)
                .withClaim("sys_username", username)
                .build();
        verifier.verify(token);
        return true;
    }

    /**
     * 解析token,擷取使用者名
     */
    public static String getUsername(String token) {
        DecodedJWT decode = JWT.decode(token);
        return decode.getClaim("sys_username").asString();
    }
}
           

自定義token

用自定義的token取代shiro中的token,例如前面的UsernamePasswordToken

public class JwtToken implements AuthenticationToken {

    private String token;

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

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

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

自定義密碼比對器

public class JwtCredentialsMatcher extends HashedCredentialsMatcher {
	/**
     * @param info realme中傳回的是username,是以getPrincipals()擷取的是使用者名
     */
    @Override
    public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo info) {
        JwtToken jwtToken = (JwtToken) authenticationToken;
        String token = jwtToken.getCredentials().toString();
        try {
            return JwtUtil.verify(token, info.getPrincipals().toString());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("token解析失敗");
        } catch (TokenExpiredException e) {
            throw new RuntimeException("token過期");
        }
    }
}
           

自定義過濾器

public class JwtFilter extends AccessControlFilter {

    /**
     * 對跨域提供支援
     */
    @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);
    }

    /**
     * 是否允許通路
     * isAccessAllowed傳回false後,去執行onAccessDenied方法
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnsupportedEncodingException {
        Subject subject = SecurityUtils.getSubject();
        String token = HttpUtil.getToken((HttpServletRequest) request);
        if (StringUtils.isNotBlank(token)) {
            JwtToken jwtToken = new JwtToken(token);
            try {
                subject.login(jwtToken);
                return true;// 登入成功
            } catch (Exception e) {
                // 登入失敗
                throw new RuntimeException("登入失敗");
            }
        } else {
            // 沒有token
            return false;
        }
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        String token = HttpUtil.getToken((HttpServletRequest) servletRequest);
        if (StringUtils.isNotBlank(token)) {
            String username = JwtUtil.getUsername(token);
            if (JwtUtil.verify(token, username)) {
                // 沒有權限

            }
        } else {
            // 沒有token

        }
        return false;
    }
}
           

修改shiro配置類

@Bean("jwtFilter")
public JwtFilter jwtFilter() {
    return new JwtFilter();
}

@Bean("credentialsMatcher")
public JwtCredentialsMatcher credentialsMatcher() {
    return new JwtCredentialsMatcher();
}

@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
    ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
    filter.setSecurityManager(securityManager);

    Map<String, Filter> filterMap = new LinkedHashMap<>();
    filterMap.put("jwt", new JwtFilter());
    filter.setFilters(filterMap);

    LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    // login接口不需要認證
    filterChainDefinitionMap.put("/auth/login", "anon");
    filterChainDefinitionMap.put("/**", "jwt");
    filter.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return filter;
}
           

加入自定義的過濾器和密碼比對器,所有接口都需要執行自定義過濾器。

修改自定義realme

@Slf4j
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private SysUserService userService;
    @Autowired
    private SysRoleService roleService;
    @Autowired
    private SysPermissionService permissionService;

    // 不寫該方法,會報錯不支援自定義的token
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 授權
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        String username = principalCollection.getPrimaryPrincipal().toString();
        SysUser sysUser = userService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
        // 去資料庫中查詢該使用者的角色和權限
        List<SysRole> roles = roleService.getRoles(sysUser.getId());
        List<SysPermission> permissions = permissionService.getPermissions(sysUser.getId());

        Set<String> permissionSet = new HashSet<>();
        Set<String> roleSet = new HashSet<>();

        permissions.forEach(item -> permissionSet.add(item.getCode()));
        roles.forEach(item -> roleSet.add(item.getCode()));

        authorizationInfo.addRoles(roleSet);
        authorizationInfo.addStringPermissions(permissionSet);
        return authorizationInfo;
    }

    /**
     * 登入認證
     * @param authenticationToken 自定義的token
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // authenticationToken就是JwtToken
        if (authenticationToken.getPrincipal() == null) {
            throw new AuthenticationException("token不合法");
        }
        String token = authenticationToken.getPrincipal().toString();
        String username = JwtUtil.getUsername(token);
        SysUser one = userService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
        if (one == null) {
            // 沒有該使用者名
            log.error("沒有該使用者名: {}", username);
            throw new AuthenticationException("使用者不存在");
        }
        // 使用者狀态判斷
        if (one.getStatus() == 1) {
            throw new AccountException("使用者被禁用");
        }
        if (one.getIsDelete() == 1) {
            throw new AccountException("使用者被删除");
        }
        // 其他業務判斷
        // 判斷通過後,将資料庫中查詢出來的user封裝為info
        return new SimpleAuthenticationInfo(
                one.getUsername(),// 這個參數是什麼,在後續的subject.getPrincipal就是什麼
                one.getPassword(),
                ByteSource.Util.bytes(one.getUsername()),// 密碼加密的"鹽值",可以是username、id等
                getName()
        );
    }
}
           

修改登入代碼

@Override
public String login(SysUser sysUser) {
    // 非空判斷
    if (sysUser == null) {
        return null;
    }
    if (sysUser.getUsername() == null || sysUser.getPassword() == null) {
        return null;
    }
    try {
        String token = JwtUtil.createToken(sysUser.getUsername(), 1440000L);
        Subject subject = SecurityUtils.getSubject();
        JwtToken jwtToken = new JwtToken(token);
        subject.login(jwtToken);
        return token;
    } catch (AuthenticationException e) {
        log.error("登入失敗");
        throw new AuthenticationException("登入失敗");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return "登入失敗";
}
           

測試

SpringBoot整合Shiro+Jwt實作登入認證和授權shiro整合JWT總結

登入成功後,會傳回token。

SpringBoot整合Shiro+Jwt實作登入認證和授權shiro整合JWT總結

攜帶token調用接口,成功傳回資料

SpringBoot整合Shiro+Jwt實作登入認證和授權shiro整合JWT總結

通路不具備權限的接口

控制台報錯:

org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method: public java.lang.String com.shiro.controller.AuthController.userView()

總結

到此,shiro部分和shiro整合jwt部分完成。這隻是一個例子,代碼中還有很多需要完善的地方,比如:傳回結果的封裝、對異常的處理等。

另外一般在系統中,token過期重新整理也是必不可少的。

繼續閱讀