Shiro实现权限认证

772 阅读6分钟

♥♥哈哈哈我来了

今天我们来讲讲使用Shiro来实现用户登陆权限

简介:Shiro

介绍: Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

主要功能 三个核心组件:Subject, SecurityManager 和 Realms. Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。 Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。  SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。  Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。  从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。  Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

直接走上正题:

第一步:依旧是引入我们的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.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.2.2</version>
            <exclusions>
                <exclusion>
                    <groupId>com.puppycrawl.tools</groupId>
                    <artifactId>checkstyle</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

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

是不是感觉很熟悉啊,这些都是我们常用的Pom文件哦。。

第二步:application.yml文件就不用多少来吧。。还是上代码吧:

​ 启动端口

server:
    port: 8888
spring:
  application:
    name: demo-shiro

这也太简单了吧,使用SpringBoot开发就是这么好哦。。

第四步:注意了,开胃菜来了。。。。

创建User对象:(User)

package com.demo.shiro.entity;

import lombok.Data;

import java.util.Set;

@Data
public class User {

    /**
     * 用户Id
     */
    private Long userId;

    /**
     * 用户名称
     */
    private String userName;
    
    /**
     * 用户密码
     */
    private String userPassword;

    /**
     * 用户角色
     */
    private Set<String> role;

    /**
     * 用户权限
     */
    private Set<String> permission;

    /**
     * 构造器
     * @param userId
     * @param userName
     * @param userPassword
     * @param role
     * @param permission
     */
    public User(Long userId, String userName, String userPassword, Set<String> role, Set<String> permission) {
        this.userId = userId;
        this.userName = userName;
        this.userPassword = userPassword;
        this.role = role;
        this.permission = permission;
    }
}

创建数据响应:(Response)

package com.demo.shiro.entity;

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

/**
 * 响应数据
 */
public class Response extends HashMap<String, Object> {
/**
     * 信息
     * @param message
     * @return
     */
    public Response message(String message){

        this.put("message", message);

        return this;
    }

    /**
     * 数据
     * @param data
     * @return
     */
    public Response data(Object data){

        this.put("data", data);

        return this;
    }

    /**
     * 重新方法
     * @param key
     * @param value
     * @return
     */
    @Override
    public Object put(String key, Object value) {

        super.put(key, value);

        return this;
    }
}

创建我们自定义的异常(SystemException)

package com.demo.shiro.exception;

/**
 * 异常抛出
 */
public class SystemException extends Exception{

    public SystemException(String messgae){

        super(messgae);
    }
}

创建一个系统工具类类模拟数据信息:(SystemUtil):

package com.demo.shiro;

import com.demo.shiro.entity.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import javax.lang.model.type.ArrayType;
import java.util.*;

/**
 * 模拟数据库数据
 */
public class SystemUtils {
 /**
     * 模拟数据库中两条数据
     *
     * @return
     */
    public static List<User> users() {
    
    List<User> users = new ArrayList<>();

        // 添加数据
        users.add(new User(
                1L,
                "admin",
                "123456",
                new HashSet<>(Collections.singleton("admin")),
                new HashSet<>(Arrays.asList("user:add", "user:delete"))));

        users.add(new User(
                2L,
                "register",
                "123456",
                new HashSet<>(Collections.singleton("register")),
                new HashSet<>(Arrays.asList("user:view"))));

        return users;
    }

    /**
     * 获取用户
     * @param username
     * @return
     */
    public static User getUser(String username){

        List<User> users = SystemUtils.users();

        return users.stream().filter(user ->

            StringUtils.equalsAnyIgnoreCase(username,user.getUserName())).findFirst().orElse(null);
    }
}  

OK,到这里我们基本类就全部创建完成了,下面开始主角来了。。。

第五步:创建我们的Shiro相关配置信息

Shiro配置类:(ShiroConfig)

package com.demo.shiro.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
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.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.*;

@Configuration
public class ShiroConfig {

 @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 设置 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager(ShiroRealm shiroRealm) {

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 配置 SecurityManager,并注入 shiroRealm
        securityManager.setRealm(shiroRealm);

        return securityManager;
    }
    
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     *  解决注解不生效的问题
     * @return
     */
    @Bean
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
        return new DefaultAdvisorAutoProxyCreator();
    }
}

Shiro认证和授权类(ShiroRelam):

package com.demo.shiro.config;

import com.demo.shiro.SystemUtils;
import com.demo.shiro.entity.User;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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.stereotype.Component;

@Component
public class ShiroRealm extends AuthorizingRealm {

 /**
     * 登陆模块
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        // 获取用户名
        String username = (String) authenticationToken.getPrincipal();

        // 获取密码
        String password = new String((char[]) authenticationToken.getCredentials());

        // 根据用户名获取用户
        User user = SystemUtils.getUser(username);

        if (user == null || !StringUtils.equals(password, user.getUserPassword())){

            throw new IncorrectCredentialsException("用户名或密码错误");
        }

        // 登陆成功
        return new SimpleAuthenticationInfo(user, user.getUserPassword(), getName());
    }

    /**
     * 授权模块
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        User user = (User) SecurityUtils.getSubject().getPrincipal();

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        // 设置角色,模拟数据库
        simpleAuthorizationInfo.setRoles(user.getRole());

        // 设置权限
        simpleAuthorizationInfo.setStringPermissions(user.getPermission());

        return simpleAuthorizationInfo;
    }
}

是不是感觉很简单呢,是的,只需要配置这两个类就可以完Shiro了。。。。

第六步:现在可以开始我们的测试了。。。你准备好了吗?

创建一个登陆页面:(login.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登陆测试</title>
</head>
<body>

<form action="/login" method="post">

    <input type="text" name="username">
    <br>

    <input type="password" name="password">

    <br>

    <input type="submit" value="登陆">

</form>
</body>
</html>

创建ViewController来访问我们的页面:

package com.demo.shiro.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 页面控制
 **/

@Controller
public class ViewController {

@RequestMapping("login.html")
    public String showLogin(){

        return "login";
    }
}

这里页面不用展示了,这也太简单了吧。。。。

创建我们的登陆Controller(LoginController)

package com.demo.shiro.controller;

import com.demo.shiro.entity.Response;
import com.demo.shiro.exception.SystemException;
import com.demo.shiro.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 登陆Controller
 */
@Controller
public class LoginController {

    @Autowired(required = false)
    private LoginService loginService;

    /**
     * 登陆
     * @param username
     * @param password
     * @return
     */
    @PostMapping("login")
    public ResponseEntity<Response> login(@RequestParam("username") String username,
                                          @RequestParam("password") String password) throws SystemException {

        Response response = this.loginService.login(username, password);

        return ResponseEntity.ok(response);
    }
}

可能会有报错,不用慌张,是因为我们的LoginService没有创建。。。

创建我们的LoginService(LoginService):

package com.demo.shiro.service;

import com.demo.shiro.entity.Response;
import com.demo.shiro.exception.SystemException;

public interface LoginService {

    // 登陆
    Response login(String username, String password) throws SystemException;
}

创建它的实现类(LoginServiceImpl)

package com.demo.shiro.service.impl;

import com.demo.shiro.SystemUtils;
import com.demo.shiro.entity.Response;
import com.demo.shiro.entity.User;
import com.demo.shiro.exception.SystemException;
import com.demo.shiro.service.LoginService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;

/**
 * 登陆Service
 */
@Service
public class LoginServiceImpl implements LoginService {

 /**
     * 登陆操作
     * @param username
     * @param password
     * @return
     */
    @Override
    public Response login(String username, String password) throws SystemException {

        Subject subject = SecurityUtils.getSubject();
        
        // 判断是否存在
        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)){

            throw new SystemException("请输入信息");
        }

        try {
            // 执行登陆
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);

            subject.login(usernamePasswordToken);

            User user = (User) subject.getPrincipal();

            return new Response().message("认证成功").data(user);

        } catch (AuthenticationException e) {

            e.printStackTrace();

            throw new SystemException("认证失败");
        }

    }
}            

这里面的代码也很容易看懂吧,这代码质量太差了吧。。。。 哈哈哈,需要自己优化哦。。。

到这里,我们的登陆逻辑就完了。。。是不是感觉很容易呢。。。

接下来是我们的测试时间了,,Are you ready?

第七步:测试环境:

创建测试Controller(TestController)

package com.demo.shiro.controller;

import com.demo.shiro.entity.Response;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.security.RolesAllowed;

/**
 * 测试Controller
 */
@Controller
@RequestMapping("test")
public class TestController {

/**
     * 需要admin角色才能访问
     * @return
     */
    @GetMapping("admin")
    @RequiresRoles("admin")
    public ResponseEntity<Response> test1(){

        return ResponseEntity.ok(new Response().message("你是admin"));
    }
    
    /**
     * 拥有查看权限
     * @return
     */
    @GetMapping("view")
    @RequiresPermissions("admin:view")
    public ResponseEntity<Response> test2(){

        return ResponseEntity.ok(new Response().message("你有查看权限"));
    }
}    

这里面有两个没有遇见过的注解吧,那么学习了这篇文章你就会了哦。。。。

这里只是实现了登陆,没有设置过滤器、拦截器这些。。。 我们需要登陆过后才能访问页面。。。有没有想过呢。。。 哈哈哈

目录截图:

测试截图:

登陆界面:

使用admin做测试登陆: 登陆成功后我们会看到以下信息

我相信看得懂吧,没错就是我们自己设置的模拟数据哦。。 接下来我们访问其他功能。。。 访问test/admin这个接口: 我们再访问test/view这个接口: 你是否看到这个页面很慌呢,不要慌,我们要稳住,仔细一看,原来我们没有这个权限,所以。。。。懂吧

OK,这里我们演示完了,还有个注册用户,我们可以自己测试。。。 GitHub地址:Shiro-demo 好啦,写了这么久终于写完了,,,,好累哦。。。 有问题欢迎在评论去评论,,,,感谢你的浏览。。。。么么哒