springBoot的shiro的简单项目部署

springboot2.0框架下使用Shiro

介绍

Shiro是Apache旗下的开源项目,是一个简单易用的安全框架,提供包括认证、授权、加密、会话管理等诸多功能。Shiro使用了比较简单易懂易于使用的授权方式。Shiro属于轻量级框架,配置简单,应用广泛。在很多优秀的开源项目之中都有使用。

搭建springboot,网页部分

创建项目

搭建springboot开发环境

打开idea,点击文件(file)-->新建(new)-->新建文件(Project)

shiro-01.png

之后就会进入idea的新建项目之中,这里点击这个树叶加一个开关按钮的这个标志,之后点击下一步就可以了

shiro-02.png

之后这个就是,真正的配置界面,填好信息,就可以点击下一步,这里面比较重要的是这个javaVersion这里,默认是11版本。需要用户自己根据实际情况选择版本

shiro-03.png

在依赖选择这里,只选Web里面的spring web选项就可以了。然后点击下一步。

shiro-04.png

这里一般只看上面就可以了,项目名和项目位置最后面的名字可以不一致,不影响程序的运行

shiro-05.png

点击完成,再等maven项目下载所需的依赖就可以了。依赖下载完成,项目结构如下。(target在项目运行时,会生成)

shiro-06.png

添加依赖项

打开pom.xml文件在标签内添加依依赖

org.apache.shiro.shiro-all是本文中需要使用的的依赖

com.alibaba.fastjson是一个json格式文件处理工具,可能会用到


    org.apache.shiro
    shiro-all
    1.6.0


    com.alibaba
    fastjson
    1.2.74

打开idea右侧的maven的,点击如图所示刷新按钮,下载依赖包

shiro-07.png

搭建网页前端内容

用几个前端网页,对接后端数据,方便调试。

第一个是首页面(需要登录才能进入)

第二个是用户的登录页面

第三个是提示用户没有授权的页面,他会自动跳转到首页面或者登录页面。

首先是第一个页面,简单的使用html网页写一个大大的首页,并对其简单的美化一下,非常清楚




    
    首页


首页主体

然后是第二个页面,登录页面,使用form表单,action内容千万不要使用域名(ip)+端口之后是路径这种方式,则会导致每次提交表单,都会导致session不一致,直接写路径就好。还是死一样,简单的美化一下就好




    
    系统登录
    


用户名
密码

之后就是用户没有权限时跳转的页面,这里设置为打开页面5秒后,自动跳转到首页面或者登录页面。




    
    你没有该权限访问






java部分

介绍

考虑到sql,mybatis,配置过于繁杂,所以项目中使用静态的数据代替sql。再模拟写一个service层的部分。

启动项

在项目的启动项Application类中设置一个新注解,这里使用@ComponentScan注解来扫描注解类,为了省事,直接使用**号扫描

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = {
        //扫描包,加载注解
        "com.eelinker.shiro.**",
        "com.eelinker.shiro.**.**",
        "com.eelinker.shiro.**.**.**"
})
public class ShiroApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShiroApplication.class, args);
    }
}

Service层

User

首先需要创建一个实体类,User,用户的数据信息封装,get和set以及toString方法省略。这里面有一个Role的类是没有定义的,代表的是用户的访问权限。

public class User {
    //用户id
    private int id;
    //用户名
    private String username;
    //用户密码
    private String password;
    //用户权限
    private Role role;
}

Role

这里设定了老板权限,员工权限,检查人员权限,以及顾客权限三大类型,详细的权限还分为管理权(administration),制作权(make),检查权(check)

顾客只能访问最基本的访问权限,其他的人都默认拥有顾客的访问权限,这些访问权限都需要登录,不登录都会默认跳转到登录界面。为了区分这些权限,特地的做了一个枚举类(不用枚举也行,用字符串数据代替也是可以的)

package com.eelinker.shiro.entity;

import java.util.HashSet;
import java.util.Set;

/**
 *角色类型
 *
 * @author Administrator
 */
public enum Role {

    //老板 所有权限
    boss(permissionType.administration, permissionType.make, permissionType.check),

    //员工 制作权限
    staff(permissionType.make),

    //检查人员 查询权限
    checker(permissionType.check),

    //顾客 使用权限
    customer;

    private final permissionType[] types;

    Role(permissionType... types) {
        this.types = types;
    }
    /**
     * 权限类型
     */
    public enum permissionType {
        administration,
        make,
        check;

        @Override
        public String toString() {
            return name();
        }
    }
    /**
     * 获取用户的权限名称
     */
    public Set toSetValue() {
        Set permsSet = new HashSet<>();
        for (permissionType type : types) {
            System.out.println(type.name());
            permsSet.add(type.name());
        }
        return permsSet;
    }
}

UserService

在将用户的信息封装好之后,接下来就是写Service的代码了,因为这里只是演示,所以只需要展示用户的访问权限就行了。静态的List集合代替数据库中的内容,select代替mybatis的查询语句封装。根据用户名查询用户的具体数据。

import com.eelinker.shiro.entity.Role;
import com.eelinker.shiro.entity.User;
import org.springframework.stereotype.Service;

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

/**
 * Created System: Windows7
 * Created Code by  Administrator
 * Created Date: 2022/1/13 16:05
 *
 * @author Administrator
 */
@Service
public class UserService {
    private static List userList = new ArrayList() {
        {
            add(new User(1, "admin", "admin123", Role.boss));
            add(new User(2, "checker", "checker123", Role.checker));
            add(new User(3, "staff_zhang", "staff003", Role.staff));
            add(new User(4, "staff_li", "staff004", Role.staff));
            add(new User(5, "staff_wang", "staff005", Role.staff));
            add(new User(6, "staff_zhao", "staff006", Role.staff));
            add(new User(7, "customer7", "customer", Role.customer));
            add(new User(8, "customer8", "customer", Role.customer));
            add(new User(9, "customer9", "customer", Role.customer));
            add(new User(10, "customer10", "customer", Role.customer));
            add(new User(11, "customer11", "customer", Role.customer));
        }
    };

    private static User select(String username) {
        for (User user : userList) {
            if (user.getUsername().equals(username)) {
                return user;
            }
        }
        return null;
    }

}

shiro配置部分

读取yml配置文件数据内容

在springboot中是可以自己设置yml中的键值对的。这样方便开发者修改,调试。这里是我自定义的shiro配置。使用shiro作为前缀。在shiro的下一级子参数中,login-url这个key值写成loginUrl也是可以的。

配置文件的名称定位application-shiro.yml(后缀改为yaml也是可以的)

shiro:
  #登录地址url
  login-url: /login.html
  #未经授权的跳转
  unauthorized-url: /unauthor.html
  #登录成功的url
  success-url: /index.html
  filter-class:
    - anon,org.apache.shiro.web.filter.authc.AnonymousFilter
    - logout,org.apache.shiro.web.filter.authc.LogoutFilter
  #路径过滤规则
  filter-rule:
    - /**.html,anon
    - /index/**,anon
    - /user/**,anon
    #注销登录的地址,不需要开发者写相关的代码或业务,也能退出登录
    - /logout,logout
    #需要有这个名称的权限才能访问这个路径(这里没有提登录,不登录也会触发,之后跳转到unauthorized-url)
    - /select/**,perms["check"]
    - /**,authc

在写好了配置之后,这还是不能让开发者直接读取。需要在主配置相中配置,也就是application.yml这个文件。

下面这里server.port这里是修改项目端口的位置

spring.profiles.active这里是配置springboot,读取上面配置的application-shiro.yml文件。

server:
  port: 8052
spring:
  profiles:
    active: shiro
  messages:
    encoding: UTF-8
  main:
    allow-bean-definition-overriding: true

在配置好上面信息之后,就可以创建一个实体类封装这些数据了。代码如下。因为配置中需要的是一组键值对而不是list集合,所以可以用“,”隔开两个两个数据前面的是路径和面的是具体的内容

filterClass是拦截器的类加载,在getFilterClassValue方法中会创建出这一个类,然后返回。

filterRule这是拦截路径,前面的是路径,后面的是允许访问的条件

@Component
@ConfigurationProperties(prefix = "shiro")
public class ShiroConfigReader {
    private String loginUrl;
    private String unauthorizedUrl;
    private String successUrl;
    private List filterClass;
    private List filterRule;
    // get set toString省略,在idea中可以使用alt+insert键创建
    public Map getFilterClassValue() throws Exception {
        Map map = new HashMap<>();
        if (filterClass != null) {
            for (String string : filterClass) {
                String[] keyAndValue = string.split(",");
                if (keyAndValue.length == 2) {
                    Class aClass = ClassLoader.getSystemClassLoader().loadClass(keyAndValue[1]);
                    Object object = aClass.newInstance();
                    if (object instanceof Filter) {
                        map.put(keyAndValue[0], (Filter) object);
                    }
                }
            }
            if (map.size() != 0) {
                return map;
            }
        }
        throw new IllegalAccessException("The parameter is empty or the value is incorrect");
    }

    public Map getFilterRuleMap() {
        Map map = new HashMap<>();
        if (filterRule != null) {
            for (String string : filterRule) {

                String[] keyAndValue = new String[2];
                int index = string.indexOf(",");
                if (index != -1) {
                    keyAndValue[0] = string.substring(0, index);
                    keyAndValue[1] = string.substring(index + 1);
                    map.put(keyAndValue[0], keyAndValue[1]);
                } else {
                    throw new IllegalArgumentException("The yml file parameter is incorrect");
                }
            }
            return map;
        }
        throw new IllegalArgumentException("The yml file parameter is incorrect");
    }
}

拿到这些配置信息之后,就可以根据内容读取数据信息,之后的修改和添加都可以在yml中进行。之前的上一个java文件中有@Component这个注解,所以在下面的shiro中可以使用@Autowired读取。在shiroFilterFactoryBean读取加载这些信息

import com.eelinker.shiro.config.reader.ShiroConfigReader;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
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.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * springBoot整合jwt实现认证有三个不一样的地方,对应下面abc
 *
 * @author Administrator
 */
@Configuration
public class ShiroConfig {
    @Autowired
    private ShiroConfigReader reader;

    @Bean
    public Realm realm() {
        return new MyRealm();
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());
        // 关闭 ShiroDAO 功能
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        // 不需要将 Shiro Session 中的东西存到任何地方(包括 Http Session 中)
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        //禁止Subject的getSession方法
//        securityManager.setSubjectFactory(subjectFactory());
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean() throws Exception {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager());
        shiroFilter.setLoginUrl(reader.getLoginUrl());
        shiroFilter.setUnauthorizedUrl(reader.getUnauthorizedUrl());
        shiroFilter.setFilters(reader.getFilterClassValue());
        shiroFilter.setFilterChainDefinitionMap(reader.getFilterRuleMap());
        return shiroFilter;
    }
}

在第一个的@Bean中里面创建了一个MyRealm这个类的对象。这里就是用户的授权认证所需要用到的内容。

doGetAuthorizationInfo方法,判断当前访问的用户是否具有访问权限,例如检查,管理制作权限。这里会查询用户的数据信息,使用到数据库验证,读取用户信息进行验证。

doGetAuthenticationInfo这个方法师在用户调用subject.login(token)时,会进入到这里进行权限的配置。


import com.eelinker.shiro.entity.User;
import com.eelinker.shiro.service.UserService;
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.beans.factory.annotation.Autowired;

import java.util.Set;

public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserService service;

    /**
     * 用户身份认证
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        String username = (String) principalCollection.getPrimaryPrincipal();
        User user = service.selectByUsername(username);
        // 权限Set集合
        Set permsSet = user.getRole().toSetValue();
        // 返回权限
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(permsSet);
        return info;
    }

    /**
     * 用户权限认证(登录)
     * 这个token就是从过滤器中传入的jwtToken
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //这里返回的是类似账号密码的东西,但是jwtToken都是jwt字符串。还需要一个该Realm的类名
        return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), "MyRealm");
    }

}

但是查询用户所拥有的权限这里,没有userService.selectByUsername方法,所以到userService中添加一个这个方法

public User selectByUsername(String username) {
    for (User user : userList) {
        if (user.getUsername().equals(username)) {
            return user;
        }
    }
    return null;
}

Controller简单创建

在前面创建的页面中有一个登录页面,那么现在就进行登录操作。类的创建就省略了,这里自动注入了一个UserService。mapping中可以验证

@Autowired
private UserService userService;

/**
 * 用户登录
 *
 * @param username
 * @param password
 * @return
 */
@RequestMapping("/user/login")
public String login(@RequestParam String username, @RequestParam String password) {
    User user = userService.login(username, password);
    if (user != null) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        subject.login(token);
        return "redirect:/index.html";
    }
    return "redirect:/login.html";
}

但是目前没有写验证的service代码。回到UserService添加如下代码,验证用户名和密码是否正确。

public User login(String username, String password) {
    User user = select(username);
    if (user == null) {
        throw new NullPointerException("User does not exist !");
    }
    if (user.getPassword().equals(password)) {
        return user;
    } else {
        System.out.println("密码错误");
    }
    return null;
}

从下图中可以看到,先输入的纯域名,之后跳转到了login.html就表名shiro的拦截页面起了效果

shiro-08.png

好,看看登录页面,输入下面数据,查看是否登录成功,成功就会进入index.html页面

shiro-09.png
shiro-10.png

回到代码,写一个需要有权限才能访问的页面,这里面的select在之前的yml文件中是已经配置过的了。需要有查询权限才能访问。代码写好重启,去网页上访问

@ResponseBody
public Map getInfo() throws Exception {
    Map map = new HashMap<>();
    map.put("filterRule", reader.getFilterRuleMap());
    map.put("filterClass", reader.getFilterClassValue());
    map.put("loginUrl", reader.getLoginUrl());
    map.put("unauthorizedUrl", reader.getUnauthorizedUrl());
    map.put("successUrl", reader.getSuccessUrl());
    return map;
}

网页输入localhost:8052/select/info,执行访问,由于已经重启,多以他会跳转到登录页面,这时候需要重新登录。(如果还是在首页面,那就是网页缓存的问题,清一下就好)

如果出现一下的状态,就是没问题的

shiro-11.png

如果用户没用登录,或者登录了但是没有这个查验的权利呢?用户呢?

输入localhost:8052/logout注销登录,再次输入localhost:8052/select/info尝试。

没登录的用户不能访问,会跳转到登录页面,而登录的用户,却没有这个权限的,会跳转到如下页面,之后再跳转到登录页面

shiro-12.png

你可能感兴趣的:(springBoot的shiro的简单项目部署)