使用Shiro实现认证和授权(基于SpringBoot)

使用Shiro实现认证和授权(基于SpringBoot)

Apache Shiro是一个功能强大且易于使用的Java安全框架,它为开发人员提供了一种直观,全面的身份验证,授权,加密和会话管理解决方案。下面是在SpringBoot中使用Shiro进行认证和授权的例子,代码如下:

pom.xml

导入SpringBoot和Shiro依赖:

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

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

也可以直接导入Apache Shiro提供的starter:

<dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-web-starter</artifactId>
</dependency>

Shiro配置类

package com.cf.shiro1.config;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //设置未认证(登录)时,访问需要认证的资源时跳转的页面
        shiroFilterFactoryBean.setLoginUrl("/loginPage");

        //设置访问无权限的资源时跳转的页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorizedPage");
        
        //指定路径和过滤器的对应关系
        Map<String, String> filterMap = new HashMap<>();
        //设置/user/login不需要登录就能访问
        filterMap.put("/user/login", "anon");
        //设置/user/list需要登录用户拥有角色user时才能访问
        filterMap.put("/user/list", "roles[user]");
        //其他路径则需要登录才能访问
        filterMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("realm") Realm realm) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(realm);
        return defaultWebSecurityManager;
    }

    @Bean
    public Realm realm() {
        MyRealm realm = new MyRealm();
        //使用HashedCredentialsMatcher带加密的匹配器来替换原先明文密码匹配器
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //指定加密算法
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        //指定加密次数
        hashedCredentialsMatcher.setHashIterations(3);
        realm.setCredentialsMatcher(hashedCredentialsMatcher);
        return realm;
    }
}

自定义Realm

package com.cf.shiro1.config;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

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

public class MyRealm extends AuthorizingRealm {
    /**
     * 授权
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Object username = principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(getRoles(username.toString()));
        return simpleAuthorizationInfo;
    }

    /**
     * 认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        String username = token.getUsername();
        Map<String, Object> userInfo = getUserInfo(username);
        if (userInfo == null) {
            throw new UnknownAccountException();
        }

        //盐值,此处使用用户名作为盐
        ByteSource salt = ByteSource.Util.bytes(username);

        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, userInfo.get("password"), salt, getName());
        return authenticationInfo;
    }

    /**
     * 模拟数据库查询,通过用户名获取用户信息
     *
     * @param username
     * @return
     */
    private Map<String, Object> getUserInfo(String username) {
        Map<String, Object> userInfo = null;
        if ("zhangsan".equals(username)) {
            userInfo = new HashMap<>();
            userInfo.put("username", "zhangsan");

            //加密算法,原密码,盐值,加密次数
            userInfo.put("password", new SimpleHash("MD5", "123456", username, 3));
        }
        return userInfo;
    }

    /**
     * 模拟查询数据库,获取用户角色列表
     *
     * @param username
     * @return
     */
    private Set<String> getRoles(String username) {
        Set<String> roles = new HashSet<>();
        roles.add("user");
        roles.add("admin");
        return roles;
    }
}

Controller

package com.cf.shiro1.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {

    /**
     * 登录
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/login")
    public String userLogin(String username, String password) {
        String result;

        //获取当前用户
        Subject currentUser = SecurityUtils.getSubject();

        //用户是否已经登录,未登录则进行登录
        if (!currentUser.isAuthenticated()) {
            //封装用户输入的用户名和密码
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);

            try {
                //登录,进行密码比对,登录失败时将会抛出对应异常
                currentUser.login(usernamePasswordToken);
                result = "登录成功";
            } catch (UnknownAccountException uae) {
                result = "用户名不存在";
            } catch (IncorrectCredentialsException ice) {
                result = "密码错误";
            } catch (LockedAccountException lae) {
                result = "用户状态异常";
            } catch (AuthenticationException ae) {
                result = "登录失败,请与管理员联系";
            }
        } else {
            result = "您已经登录成功了";
        }

        return result;
    }

    @RequestMapping("/list")
    public String userList() {
        return "访问我需要登录并且需要拥有user角色!";
    }
}