分类:Spring Boot

wmanage-基于Spring Boot+MyBatis+Shiro+LayUI的后台管理系统

之前的 wAdmin-基于Spring Boot+MyBatis+Layui的后台管理系统  废弃,原因就是别人的东西改不来,都是bug,不如自己动手,哈哈哈。

经过一段时间的重构,终于可以用了,测试也没有bug,以后也会慢慢更新。

详见:Github

原系统里面树形菜单采用了jTree,整了半天也没明白,索性,自己研究了zTree,开始感觉很难,做了一段时间后,发现也就那么回事,洒洒水啦,哈哈哈。这给了我一点启发,只要学习,就会有收获。

关于shiro的之前也写了一些东西,但发现是有些错误的,随着现在学习的深入,之后会进行修改的,希望理解。毕竟是一只前进中的菜鸡。

不多说了,Go!

工具类中调用Service层

我们通常抽取很多共同的方法作为一个工具类,但是有的工具类,可能会用到Service层的方法来查找对象或者其他操作,工具类的方法是静态的,Service不能是静态,怎么办呢?下面是一种可行的办法:

直接看工具类:

/**
 * @description ...
 * @author  wujianyu
 * @email wjy329@vip.qq.com
 * @date  2019年1月18日 上午10:42:31
 */
package com.wjy329.utils;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.wjy329.service.WjyService;


@Component
public class WUtil {
	
	private static WUtil wUtil ;
	
	@Autowired
	private WjyService wjyService;
	
	@PostConstruct
	public void init() {
	    wUtil = this;
	}

    public static void function(){
      wUtil.wjyService.add();
    }

}

注意初始化时,加上  @PostConstruct  注解。这样就可以了。

Spring Boot之Kaptcha验证码使用

1.引入Kaptcha依赖

<!-- 验证码 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

2.KaptchaBean.java 配置文件

package com.wjy329.shiro.filter;

import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.servlet.KaptchaServlet;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class KaptchaBean {
    @Bean
    public org.springframework.boot.web.servlet.ServletRegistrationBean kaptchaServlet() {

        org.springframework.boot.web.servlet.ServletRegistrationBean registrationBean = new org.springframework.boot.web.servlet.ServletRegistrationBean(new KaptchaServlet(), "/kaptcha.jpg");

        registrationBean.addInitParameter(Constants.KAPTCHA_SESSION_CONFIG_KEY,
                Constants.KAPTCHA_SESSION_KEY);
        registrationBean.addInitParameter(Constants.KAPTCHA_IMAGE_HEIGHT, "38");//高度
        registrationBean.addInitParameter(Constants.KAPTCHA_IMAGE_WIDTH, "80");//宽度
        registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "26");//字体大小
        registrationBean.addInitParameter(Constants.KAPTCHA_BORDER_THICKNESS, "1"); //边框
        registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "red"); //文字颜色

        //可以设置很多属性,具体看com.google.code.kaptcha.Constants
//      kaptcha.border  是否有边框  默认为true  我们可以自己设置yes,no
//      kaptcha.border.color   边框颜色   默认为Color.BLACK
//      kaptcha.border.thickness  边框粗细度  默认为1
//      kaptcha.producer.impl   验证码生成器  默认为DefaultKaptcha
//      kaptcha.textproducer.impl   验证码文本生成器  默认为DefaultTextCreator
//      kaptcha.textproducer.char.string   验证码文本字符内容范围  默认为abcde2345678gfynmnpwx
//      kaptcha.textproducer.char.length   验证码文本字符长度  默认为5
//      kaptcha.textproducer.font.names    验证码文本字体样式  默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
//      kaptcha.textproducer.font.size   验证码文本字符大小  默认为40
//      kaptcha.textproducer.font.color  验证码文本字符颜色  默认为Color.BLACK
//      kaptcha.textproducer.char.space  验证码文本字符间距  默认为2
//      kaptcha.noise.impl    验证码噪点生成对象  默认为DefaultNoise
//      kaptcha.noise.color   验证码噪点颜色   默认为Color.BLACK
//      kaptcha.obscurificator.impl   验证码样式引擎  默认为WaterRipple
//      kaptcha.word.impl   验证码文本字符渲染   默认为DefaultWordRenderer
//      kaptcha.background.impl   验证码背景生成器   默认为DefaultBackground
//      kaptcha.background.clear.from   验证码背景颜色渐进   默认为Color.LIGHT_GRAY
//      kaptcha.background.clear.to   验证码背景颜色渐进   默认为Color.WHITE
//      kaptcha.image.width   验证码图片宽度  默认为200
//      kaptcha.image.height  验证码图片高度  默认为50
        return registrationBean;

    }
}

3.前端页面

<div class="layui-form-item">
    <div class="layui-inline">
    <label class="layui-form-label">验证码</label>
    <div class="layui-input-inline">
        <input type="text" name="captcha" required lay-verify="required" placeholder="请输入验证码" autocomplete="off" class="layui-input" style="width: 110px;float: left">
        <img src="kaptcha.jpg" name="captcha"  id="captcha" style="float: right" onclick="refreshCode(this)"/>
    </div>
</div>

这里的 onclick="refreshCode(this)"  是点击刷新验证码;

配置完上面3步可以访问你的页面看看验证码是否显示,如果显示则配置成功,就可以进行后续判断验证码阶段。

image.png

4.判断验证码

我这里由于使用了shiro(其他也一样),我简化了判断的方法,直接在你登陆的控制器内判断即可,Kaptcha会把前端的验证码信息存放到session中,所有我们在登陆的控制器用session中的验证码和前端表单传过来的验证码比较即可;

public String loginUser(@RequestParam("username")String username,
                        @RequestParam("password")String password,
                        @RequestParam("captcha") String captcha,
                        HttpSession httpSession){
    String sessionCaptcha = httpSession.getAttribute("KAPTCHA_SESSION_KEY").toString();
    if(!captcha.equals(sessionCaptcha)){
        return WebUtils.getInstance().writeMsg(ResultCode.ERROR, "验证码输入错误");
    }
    ......
 }

上面是我的验证代码,可以参考一下,注意获取session中的验证码的key为   “KAPTCHA_SESSION_KEY”  

5.点击刷新验证码

前面也说了点击刷新,就是一个方法,需要加入时间更新,代码如下:

function refreshCode(){
    var captcha = document.getElementById("captcha");
    captcha.src = "/kaptcha.jpg?t=" + new Date().getTime();
}

以上就是在项目中添加验证码的方法,希望有所帮助。一些配置项可以查看第2步中的配置类或者百度(-_-)。

Spring Boot之分页

分页是我们 常用的功能,MySQL实现分页的原理 就是用limit,比如 select * from table limit 0,5;  意思就是从0行开始查询5条数据,那么要实现分页我们需要两个参数,起始位置和每页的数据量,起始位置需要计算,为了前端传值的方便,前端只需传入当前页码每页数据量即可,下面我们来具体实现:

数据准备:

-- ----------------------------
-- Table structure for person
-- ----------------------------
DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of person
-- ----------------------------
INSERT INTO `person` VALUES ('1', 'wjy', '24');
INSERT INTO `person` VALUES ('2', 'wjy1', '25');
INSERT INTO `person` VALUES ('3', '25', '25');
INSERT INTO `person` VALUES ('4', 'wjy2', '25');
INSERT INTO `person` VALUES ('5', 'wjy3', '25');
INSERT INTO `person` VALUES ('6', 'wjy4', '26');
INSERT INTO `person` VALUES ('7', 'wjy5', '27');
INSERT INTO `person` VALUES ('8', 'wjy0', '24');

image.png

1.分页实体 

PageBean.java:

package com.wjy329.pagedemo.utils;

import java.util.List;

public class PageBean<T> {
    //起始
    private int offset;
    // 当前页
    private int currentPage;
    // 每页显示的数据量
    private int pageCount;
    // 总记录数
    private int totalCount;
    // 总页数 = 总记录数 / 每页显示的行数  (+ 1)
    private int totalPage;
    // 分页查询到的数据
    private List<T> pageData;

    public int getOffset() {
        return offset;
    }

    public void setOffset(int offset) {
        this.offset = offset;
    }

    // 返回总页数
    public int getTotalPage() {
        if (totalCount % pageCount == 0) {
            totalPage = totalCount / pageCount;
        } else {
            totalPage = totalCount / pageCount + 1;
        }
        return totalPage;
    }
    public void setTotalPage(int totalPage) {
        this.totalPage = totalPage;
    }

    public int getCurrentPage() {
        return currentPage;
    }
    public void setCurrentPage(int currentPage){
        this.currentPage = currentPage;
    }
    public int getPageCount() {
        return pageCount;
    }
    public void setPageCount(int pageCount) {
        this.pageCount = pageCount;
    }
    public int getTotalCount() {
        return totalCount;
    }
    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
    }

    public List<T> getPageData() {
        return pageData;
    }
    public void setPageData(List<T> pageData) {
        this.pageData = pageData;
    }
}

2.分页工具类

PageUtils.java:

package com.wjy329.pagedemo.utils;

import java.util.List;

public class PageUtils {
    public static PageBean getPage(Integer currentPage, Integer pageCount, Integer total){
        PageBean pageBean = new PageBean();
        //如果传入的当前页面为空,那么默认为第一页
        if(currentPage == null){
            currentPage = 1;
        }
        //如果传入的数据量为空,默认显示5条
        if(pageCount == null){
            pageCount = 5;
        }
        //起始位置的计算
        int offset  = (currentPage-1)*pageCount;
        pageBean.setCurrentPage(currentPage);
        pageBean.setPageCount(pageCount);
        pageBean.setTotalCount(total);
        pageBean.setOffset(offset);

        return pageBean;
    }
}

3.实体类

person.java:

package com.wjy329.pagedemo.entity;


import java.io.Serializable;

public class Person implements Serializable {
    private Integer id;

    private String name;

    private Integer age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

4.PersonMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.wjy329.pagedemo.dao.PersonDao">
    <resultMap id="BaseResultMap" type="com.wjy329.pagedemo.entity.Person">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="name" jdbcType="VARCHAR" property="name" />
        <result column="age" jdbcType="INTEGER" property="age" />
    </resultMap>

    <select id="findAllByPage" parameterType="com.wjy329.pagedemo.utils.PageBean" resultMap="BaseResultMap">
        SELECT id,name,age
        FROM person
        LIMIT #{page.offset},#{page.pageCount}
    </select>

    <select id="getAllCnt"  resultType="java.lang.Integer">
    select count(id) from person
  </select>

</mapper>

5.PersonDao.java

package com.wjy329.pagedemo.dao;

import com.wjy329.pagedemo.entity.Person;
import com.wjy329.pagedemo.utils.PageBean;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface PersonDao {
    List<Person> findAllByPage(@Param(value="page")PageBean page);

    Integer getAllCnt();
}

6.PersonService.java

package com.wjy329.pagedemo.service;

import com.wjy329.pagedemo.entity.Person;
import com.wjy329.pagedemo.utils.PageBean;

import java.util.List;

public interface PersonService {
     PageBean queryByPage(Integer currentPage, Integer pageCount);
}

7.PersonServiceImpl.java

package com.wjy329.pagedemo.service.impl;

import com.wjy329.pagedemo.dao.PersonDao;
import com.wjy329.pagedemo.entity.Person;
import com.wjy329.pagedemo.service.PersonService;
import com.wjy329.pagedemo.utils.PageBean;
import com.wjy329.pagedemo.utils.PageUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PersonServiceImpl implements PersonService {

    @Autowired
    private PersonDao personDao;

    @Override
    public PageBean queryByPage(Integer currentPage,Integer pageCount) {
        Integer total = 0;
        List<Person> persons  = null;
        total = this.personDao.getAllCnt();
        PageBean pageBean = PageUtils.getPage(currentPage,pageCount,total);
        //查询出来的分页数据
        persons = this.personDao.findAllByPage(pageBean);
        pageBean.setPageData(persons);
        return pageBean;
    }
}

8.PersonController.java

package com.wjy329.pagedemo.controller;

import com.wjy329.pagedemo.entity.Person;
import com.wjy329.pagedemo.service.PersonService;
import com.wjy329.pagedemo.utils.PageBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

@Controller
@RequestMapping("/person")
public class PersonController {
    @Autowired
    private PersonService personService;

    @RequestMapping("/getAll")
    @ResponseBody
    public PageBean getAll(Integer currentPage, Integer pageCount){
        PageBean list = personService.queryByPage(currentPage, pageCount);
        return list;
    }
}

上面就是全部的代码,然后我们开始访问: localhost:80/person/getAll   由于没有传入参数,所以按工具类中的默认 值查询,也就是第一页,5条数据:

image.png

访问: localhost:80/person/getAll?currentPage=2&pageCount=5   返回第2页的5条数据

image.png

上面就是分页的简单实现,然后据需要配合前端 的页面传值和获取 数据等。

demo地址: https://github.com/wjy329/page-demo

Springboot之shiro-动态权限

多数情况下,我们都把权限存放在数据库中,然后动态的加载,这样可以操作数据库来对权限进行增删改查。这里仅仅写如何加载数据库中查询到的权限。我这里直接用权限表的资源路径来表示;

先来一个返回的实体类,包括角色名称和资源路径,分别是从角色表和权限表中查询出来。

1、RolePerm.java

package com.wjy329.shirodemo.entity;
/**

 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-04 10:15
 **/
public class RolePerm {
    //角色名称
    private String rname;
    //资源url
    private String url;

    public String getRname() {
        return rname;
    }

    public void setRname(String rname) {
        this.rname = rname;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

2、RolePermMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wjy329.shirodemo.dao.RolePermDao">
    <resultMap id="BaseResultMap" type="com.wjy329.shirodemo.entity.RolePerm">
        <result property="rname" column="rname"/>
        <result property="url" column="url"/>
    </resultMap>

    <select id="getRolePerm" resultMap="BaseResultMap">
        SELECT t1.rname as rname,t2.url as url
        FROM role t1,permission t2,permission_role t3
        WHERE
        t1.rid = t3.rid
        AND
        t2.pid = t3.pid
    </select>
</mapper>

3、RolePermDao.java

package com.wjy329.shirodemo.dao;

import com.wjy329.shirodemo.entity.RolePerm;

import java.util.List;

/**
 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-04 10:18
 **/
public interface RolePermDao{
    List<RolePerm> getRolePerm();
}

4、RolePermService.java

package com.wjy329.shirodemo.service;

import java.util.Map;

/**
 * @Author: wjy329
 * @Time: 2018/9/4上午10:00
 */
public interface RolePermService {
    /**
    * @Description:  获取角色和权限列表
    * @Param:
    * @return:
    * @Author: wjy329
    * @Date: 2018/9/4
    */
    Map<String,String> getRolePerm();
}

5、RolePErmServiceImpl.java

package com.wjy329.shirodemo.service.impl;

import com.wjy329.shirodemo.dao.RolePermDao;
import com.wjy329.shirodemo.entity.Role;
import com.wjy329.shirodemo.entity.RolePerm;
import com.wjy329.shirodemo.service.RolePermService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**

 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-04 10:12
 **/
@Service
public class RolePermServiceImpl implements RolePermService{
    @Autowired
    RolePermDao rolePermDao;
    @Override
    public Map<String, String> getRolePerm() {
        //获取权限信息
        List<RolePerm> perms = this.rolePermDao.getRolePerm();
        if(perms == null || perms.size() ==0){
            return null;
        }
        //这个地方需要优化权限列表信息
        //去掉重复的,一个权限可以有多个角色信息
        Set<String> urls = new HashSet<>(perms.size());
        for(RolePerm rolePerm:perms){
            if(!rolePerm.getUrl().equals("/")) {
                urls.add(rolePerm.getUrl());
            }
        }
        //获取到数据库存储的shiro信息
        Map<String,String> rolePermMap = new LinkedHashMap<>();
        for(String url : urls){
            //获取到一个url对应的角色信息
            Set<String> roles = this.getRoles(perms, url);
            String shiroRole = this.getShiroRole(roles);
            rolePermMap.put(url,shiroRole);
        }
        System.out.println("shuchu"+rolePermMap);
        return rolePermMap;
        }

    private String getShiroRole(Set<String> roles) {
        StringBuffer sb = new StringBuffer();
        for(String role : roles){
            sb.append(role);
            sb.append(",");
        }
        //去掉最后的一个 ,
        String rolesStr = sb.substring(0, sb.length()-1);
        String SHIRO_OR_ROLES = "authc,orRole"+"[%s]";
        return String.format(SHIRO_OR_ROLES,rolesStr);
    }

    private Set<String> getRoles(List<RolePerm> perms, String url) {
        Set<String> roles = new HashSet<String>();

        for(RolePerm perm:perms){
            if(perm.getUrl().equals(url)){
                //这个地方title 就是role的数据,直接映射上去的,没有加字段信息
                roles.add(perm.getRname());
            }
        }

        return roles;
    }

}

大概思路就是从角色表和权限表中获取到对应的角色和权限信息,一个权限资源可以对应多个角色,用户有任一角色都可访问该资源,然后需要转换成shiro中的格式上面的注释中也写了出来,直接写roles[admin,customer]的话会导致用户必须同时拥有这两种角色才能访问,所以需要自定义一个拦截器,使其有任一角色即可访问。

6、orRole 自定义拦截器-CustomRolesAuthorizationFilter.java

package com.wjy329.shirodemo.config;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

// AuthorizationFilter抽象类事项了javax.servlet.Filter接口,它是个过滤器。
public class CustomRolesAuthorizationFilter extends AuthorizationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception {
        Subject subject = getSubject(req, resp);
        String[] rolesArray = (String[]) mappedValue;

        if (rolesArray == null || rolesArray.length == 0) { //没有角色限制,有权限访问
            return true;
        }
        for (int i = 0; i < rolesArray.length; i++) {
            if (subject.hasRole(rolesArray[i])) { //若当前用户是rolesArray中的任何一个,则有权限访问
                return true;
            }
        }

        return false;
    }
}

7、加载自定义拦截器

需要在shiro的配置中添加

Map<String,Filter> map = new HashedMap();
map.put("orRole",orRole());

bean.setFilters(map);

。。。

//自定义拦截器
@Bean("orRole")
public CustomRolesAuthorizationFilter  orRole(){
   return new CustomRolesAuthorizationFilter ();

}

具体如下:

package com.wjy329.shirodemo.config;

import com.wjy329.shirodemo.service.RolePermService;
import org.apache.commons.collections.map.HashedMap;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**

 * @description: shiro配置类
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 19:54
 **/
@Configuration
public class ShiroConfig {
    @Autowired
    RolePermService rolePermService;

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactory(@Qualifier("securityManager") SecurityManager manager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //添加自定义拦截器
        Map<String,Filter> map = new HashedMap();
        map.put("orRole",orRole());
        bean.setFilters(map);
        
        
        bean.setSecurityManager(manager);

        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");
        bean.setUnauthorizedUrl("/unauth");

        LinkedHashMap<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/index","authc");
        filterChainDefinitionMap.put("/login","anon");
        filterChainDefinitionMap.put("/loginUser","anon");
        filterChainDefinitionMap.put("/logout","logout");
        filterChainDefinitionMap.put("/admin","roles[admin]");
        filterChainDefinitionMap.putAll(rolePermService.getRolePerm());
        filterChainDefinitionMap.put("/**", "roles[admin]");

        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);


        return bean;
    }

    @Bean("securityManager")
    public SecurityManager securityManager(AuthRealm authRealm){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(authRealm);
        return manager;
    }

    @Bean("authRealm")
    public AuthRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher matcher){
        AuthRealm authRealm = new AuthRealm();
        authRealm.setCacheManager(new MemoryConstrainedCacheManager());
        authRealm.setCredentialsMatcher(matcher);
        return authRealm;
    }

    @Bean("credentialMatcher")
    public CredentialMatcher credentialMatcher(){
        return new CredentialMatcher();
    }

    //自定义拦截器
    @Bean("orRole")
    public CustomRolesAuthorizationFilter  orRole(){
        return new CustomRolesAuthorizationFilter ();
    }

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

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

这样就可以读取出数据库中的权限信息了,后面如果要对权限进行增删改查即可对角色和权限表操作,动态更新的话,我觉得应该就是重新加载权限信息,具体的实现以后遇到了再说吧。

Springboot学习之shiro整合

Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理功能,可为任何应用提供安全保障 – 从命令行应用、移动应用到大型网络及企业应用。关于shiro的原理和其他的资料可以自行百度,这里仅记录shiro的用法。

准备:

开发工具:IDEA

Java版本:1.8

Mysql:5.7

1、环境搭建

maven依赖-pom.xml

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.wjy329</groupId>
   <artifactId>shiro-demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>shiro-demo</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.10.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
   </properties>

   <dependencies>
      <!-- thymeleaf模板 -->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-thymeleaf</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>1.3.2</version>
      </dependency>

      <!-- spring.thymeleaf.mode=LEGACYHTML5 -->
      <dependency>
         <groupId>net.sourceforge.nekohtml</groupId>
         <artifactId>nekohtml</artifactId>
      </dependency>

      <dependency>
         <groupId>org.apache.httpcomponents</groupId>
         <artifactId>httpclient</artifactId>
      </dependency>
      
      <!-- 数据库驱动 -->
      <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <scope>runtime</scope>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
      <!-- shiro依赖-->
      <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-core</artifactId>
         <version>1.2.5</version>
      </dependency>
      <!-- shiro spring包 -->
      <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-spring</artifactId>
         <version>1.2.5</version>
      </dependency>

      <!-- 数据库连接池-->
      <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>druid</artifactId>
         <version>1.1.0</version>
      </dependency>

      <!-- apache工具包 -->
      <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
         <version>3.5</version>
      </dependency>

      <!-- spring工具包 -->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-context-support</artifactId>
         <version>5.0.6.RELEASE</version>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>


</project>

配置文件-application.properties

## 数据库 ##
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8
## 数据库用户名和密码 ##
spring.datasource.username=root
spring.datasource.password=admin

## mybatis ##
mybatis.mapper-locations=mapper/*.xml
mybatis.type-aliases-package=com.wjy329.shirodemo.entity

#Thymeleaf
#thymeleaf start
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
#\u5f00\u53d1\u65f6\u5173\u95ed\u7f13\u5b58,\u4e0d\u7136\u6ca1\u6cd5\u770b\u5230\u5b9e\u65f6\u9875\u9762
spring.thymeleaf.cache=false
# \u5728\u6784\u5efaURL\u65f6\u9884\u5148\u67e5\u770b\u540d\u79f0\u7684\u524d\u7f00 \uff08\u9ed8\u8ba4\u5c31\u662f\u8fd9\u4e2a\uff09
spring.thymeleaf.prefix=classpath:/templates/
# \u6784\u5efaURL\u65f6\u9644\u52a0\u67e5\u770b\u540d\u79f0\u7684\u540e\u7f00.\uff08\u9ed8\u8ba4\u5c31\u662f html\u7684\u7ed3\u5c3e\u7684\uff09
spring.thymeleaf.suffix=.html
#thymeleaf end

2、使用

权限管理包括三个实体:用户、角色、权限,所以我们需要新建三个实体类,分别是:User.javaRole.javaPermission.java

User.java

package com.wjy329.shirodemo.entity;

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

/**

 * @description: 用户实体
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 14:48
 **/
public class User {
    /**
     * 用户id
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private Integer uid;

    /**
     * 用户名称
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private String username;

    /**
     * 用户密码
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private String password;

    /**
     * 用户角色
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private Set<Role> roles = new HashSet<>();

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}

Role.java:

package com.wjy329.shirodemo.entity;

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

/**

 * @description: 角色实体
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 14:49
 **/
public class Role {
    /**
     * 角色id
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private Integer rid;

    /**
     * 角色名称
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private String name;

    /**
     * 角色权限
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private Set<Permission> permissions = new HashSet<>();

    /**
     * 一个角色包含多个用户
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private Set<User> users = new HashSet<>();

    public Integer getRid() {
        return rid;
    }

    public void setRid(Integer rid) {
        this.rid = rid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Permission> getPermissions() {
        return permissions;
    }

    public void setPermissions(Set<Permission> permissions) {
        this.permissions = permissions;
    }

    public Set<User> getUsers() {
        return users;
    }

    public void setUsers(Set<User> users) {
        this.users = users;
    }
}

Permission.java:

package com.wjy329.shirodemo.entity;

/**

 * @description: 权限实体
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 14:49
 **/
public class Permission {

    /**
    * 权限id
    * @Author: wjy329
    * @Date: 2018/9/3
    */
    private Integer pid;

   /**
    * 权限名称
    * @Author: wjy329
    * @Date: 2018/9/3
    */
    private String name;

    /**
     * 资源url
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private String url;

    public Integer getPid() {
        return pid;
    }

    public void setPid(Integer pid) {
        this.pid = pid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

权限控制需要对应5张表,分别是:user(用户表)、role(角色表)、permission(权限表)、user_role(用户角色表)、permission_role(角色权限表)  这里我直接给出建表的sql,方便大家生成:

init.sql

-- 权限表 --
CREATE TABLE permission(
  pid INT(11) NOT NULL AUTO_INCREMENT,
  name VARCHAR(255) NOT NULL DEFAULT '',
  url VARCHAR (255) DEFAULT '',
  PRIMARY KEY (pid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO permission VALUES ('1','add','');
INSERT INTO permission VALUES ('2','delete','');
INSERT INTO permission VALUES ('3','edit','');
INSERT INTO permission VALUES ('4','query','');

-- 用户表 --
CREATE TABLE user(
  uid int(11) NOT NULL AUTO_INCREMENT,
  username VARCHAR(255) NOT NULL DEFAULT '',
  password VARCHAR(255) NOT NULL DEFAULT '',
  PRIMARY KEY (uid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO user VALUES ('1','admin','123');
INSERT INTO user VALUES ('2','demo','123');

-- 角色表 --
CREATE TABLE role(
  rid int(11) NOT NULL AUTO_INCREMENT,
  rname VARCHAR (255) NOT NULL DEFAULT '',
  PRIMARY KEY (rid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO role VALUES ('1','admin');
INSERT INTO role VALUES ('2','customer');

-- 权限角色关系表 --
CREATE TABLE permission_role(
  rid int(11) NOT NULL ,
  pid int (11) NOT NULL ,
  KEY idx_rid (rid),
  KEY idx_pid (pid)
)ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO permission_role VALUES ('1','1');
INSERT INTO permission_role VALUES ('1','2');
INSERT INTO permission_role VALUES ('1','3');
INSERT INTO permission_role VALUES ('1','4');
INSERT INTO permission_role VALUES ('2','1');
INSERT INTO permission_role VALUES ('2','4');

-- 用户角色关系表 --
CREATE TABLE user_role(
  uid int(11) NOT NULL ,
  pid int (11) NOT NULL ,
  KEY idx_uid (uid),
  KEY idx_rid (rid)
)ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO user_role VALUES ('1','1');
INSERT INTO user_role VALUES ('2','2');

执行完该段sql后,会生成对应的表,接下来,我们写mybatis的东西,需要在resources文件夹下新建一个mapper文件夹,用来存放对应的mapper.xml

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.wjy329.shirodemo.dao.UserDao">
    <resultMap id="userMap" type="com.wjy329.shirodemo.entity.User">
        <id property="uid" column="uid"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <collection property="roles" ofType="com.wjy329.shirodemo.entity.Role">
            <id property="rid" column="rid"/>
            <result property="rname" column="rname"/>
            <collection property="permissions" ofType="com.wjy329.shirodemo.entity.Permission">
                <id property="pid" column="pid"/>
                <result property="name" column="name"/>
                <result property="url" column="url"/>
            </collection>
        </collection>
    </resultMap>

    <select id="findByUsername" parameterType="String" resultMap="userMap">
      SELECT u.*,r.*,p.*
      FROM user u
        INNER JOIN user_role ur on ur.uid = u.uid
        INNER JOIN role r on r.rid = ur.rid
        INNER JOIN permission_role pr on pr.rid = r.rid
        INNER JOIN permission p on pr.pid = p.pid
      WHERE u.username = #{username}
    </select>
</mapper>

UserDao.java

package com.wjy329.shirodemo.dao;

import com.wjy329.shirodemo.entity.User;
import org.apache.ibatis.annotations.Param;

/**

 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 15:11
 **/
public interface UserDao {

    /**
    * @Description:  根据用户名查找用户信息
    * @Param:
    * @return:
    * @Author: wjy329
    * @Date: 2018/9/3
    */
    User findByUsername(@Param(value = "username")String username);
}

UserService.java

package com.wjy329.shirodemo.service;

import com.wjy329.shirodemo.entity.User;

/**
 * @Author: wjy329
 * @Time: 2018/9/3下午3:16
 */
public interface UserService {

    User findByUsername(String username);
}

UserServiceImpl.java

package com.wjy329.shirodemo.service.impl;

import com.wjy329.shirodemo.dao.UserDao;
import com.wjy329.shirodemo.entity.User;
import com.wjy329.shirodemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**

 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 15:17
 **/
@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserDao userDao;
    @Override
    public User findByUsername(String username) {
        return userDao.findByUsername(username);
    }
}

TestController.java

package com.wjy329.shirodemo.controller;

import com.wjy329.shirodemo.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;

/**

 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 20:11
 **/
@Controller
public class TestController {

    @RequestMapping("/login")
    public String login(){
        return "login";
    }

    @RequestMapping("/index")
    public String index(){
        return "index";
    }

    @RequestMapping("/unauth")
    @ResponseBody
    public String unauth(){
        return "未授权";
    }

    @RequestMapping("/loginUser")
    public String loginUser(@RequestParam("username")String username,
                            @RequestParam("password")String password,
                            HttpSession httpSession){
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        Subject subject = SecurityUtils.getSubject();
        try{
            subject.login(token);
            User user = (User) subject.getPrincipal();
            httpSession.setAttribute("user",user);
            return "index";
        }catch (Exception e){
            return "login";
        }
    }

    @RequestMapping("/admin")
    @ResponseBody
    public String admin(){
        return "welcome admin";
    }
}

前面的东西都是我们熟知的,接下来才是shiro真正的开始。

shiro从Realm中获取安全数据,也就是说,从Realm中获取用户、角色、权限,可以把Realm看成shiro的安全数据源。我们就从自定义Realm开始,毕竟有了数据源再看其他的才会有意义。新建一个config的配置包,这里面存放shiro的相关配置;

AuthRealm.java

package com.wjy329.shirodemo.config;

import com.wjy329.shirodemo.entity.Permission;
import com.wjy329.shirodemo.entity.Role;
import com.wjy329.shirodemo.entity.User;
import com.wjy329.shirodemo.service.UserService;
import org.apache.commons.collections.CollectionUtils;
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.ArrayList;
import java.util.List;
import java.util.Set;

/**

 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 19:18
 **/
public class AuthRealm extends AuthorizingRealm{

    @Autowired
    private UserService userService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User user = (User) principals.fromRealm(this.getClass().getName()).iterator().next();
        //权限列表
        List<String> permissionList = new ArrayList<>();
        //角色列表
        List<String> roleNameList = new ArrayList<>();
        //获取用户角色
        Set<Role> roleSet = user.getRoles();
        if(CollectionUtils.isNotEmpty(roleSet)){
            for(Role role : roleSet){
                roleNameList.add(role.getRname());
                Set<Permission> permissionSet = role.getPermissions();
                if(CollectionUtils.isNotEmpty(permissionSet)){
                    for(Permission permission : permissionSet){
                        permissionList.add(permission.getName());
                    }
                }
            }
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissionList);
        info.addRoles(roleNameList );
        return info;
    }

    //认证登录
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String username = usernamePasswordToken.getUsername();
        User user = userService.findByUsername(username);
        return new SimpleAuthenticationInfo(user,user.getPassword(),this.getClass().getName());
    }
}

我们可以看到,自定义Realm先要继承AuthorizingRealm并实现两个方法,一个是认证(AuthenticationInfo),一个是授权(AuthorizationInfo),注释中也相应的标示了出来。

比对密码还需要我们自定义CredentialMatcher

CredentialMatcher.java

package com.wjy329.shirodemo.config;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;

/**

 * @description: 自定义密码规则
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 19:50
 **/
public class CredentialMatcher extends SimpleCredentialsMatcher{
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        //用户输入的密码
        String password = new String(usernamePasswordToken.getPassword());
        //数据库中保存的密码
        String dbPassword = (String) info.getCredentials();
        return this.equals(password,dbPassword);
    }
}

上面的密码匹配规则仅仅是简单对比,实际使用中,我们的密码可能采用密码+盐或者其他加密解密认证方式,我们只需要在这里做相应的更改即可,总之就是比对输入的密码是否正确。

认证授权和密码校验都有了,这两个东西放在哪呢?那必须是放在shiro的配置中。新建一个配置类:

ShiroConfig.java

package com.wjy329.shirodemo.config;

import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

/**

 * @description: shiro配置类
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 19:54
 **/
@Configuration
public class ShiroConfig {

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactory(@Qualifier("securityManager") SecurityManager manager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);

        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");
        bean.setUnauthorizedUrl("/unauth");

        LinkedHashMap<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/index","authc");
        filterChainDefinitionMap.put("/login","anon");
        filterChainDefinitionMap.put("/loginUser","anon");
        filterChainDefinitionMap.put("/admin","roles[admin]");
        filterChainDefinitionMap.put("/**","user");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return bean;
    }
    
    @Bean("securityManager")
    public SecurityManager securityManager(AuthRealm authRealm){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(authRealm);
        return manager;
    }
    //加载自定义的realm
    @Bean("authRealm")
    public AuthRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher matcher){
        AuthRealm authRealm = new AuthRealm();
        authRealm.setCredentialsMatcher(matcher);
        return authRealm;
    }
    //加载自定义的密码匹配
    @Bean("credentialMatcher")
    public CredentialMatcher credentialMatcher(){
        return new CredentialMatcher();
    }

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

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

上面这些方法基本都是从下往上一层层被调用,我们也可以看明白是在干什么;

shiroFilterFactory方法基本就是拦截了,这里需要注意下拦截器

默认拦截器名

拦截器类

说明(括号里的表示默认值)

身份验证相关的

authc

org.apache.shiro.web.filter.authc

.FormAuthenticationFilter

基于表单的拦截器;如 “`/**=authc`”,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam:表单提交的用户名参数名( username);  passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe);  loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储 key(shiroLoginFailure);

authcBasic

org.apache.shiro.web.filter.authc

.BasicHttpAuthenticationFilter

Basic HTTP 身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application);

logout

org.apache.shiro.web.filter.authc

.LogoutFilter

退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/); 示例 “/logout=logout”

user

org.apache.shiro.web.filter.authc

.UserFilter

用户拦截器,用户已经身份验证 / 记住我登录的都可;示例 “/**=user”

anon

org.apache.shiro.web.filter.authc

.AnonymousFilter

匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例 “/static/**=anon”

授权相关的

roles

org.apache.shiro.web.filter.authz

.RolesAuthorizationFilter

角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例 “/admin/**=roles[admin]”

perms

org.apache.shiro.web.filter.authz

.PermissionsAuthorizationFilter

权限授权拦截器,验证用户是否拥有所有权限;属性和 roles 一样;示例 “/user/**=perms["user:create"]”

port

org.apache.shiro.web.filter.authz

.PortFilter

端口拦截器,主要属性:port(80):可以通过的端口;示例 “/test= port[80]”,如果用户访问该页面是非 80,将自动将请求端口改为 80 并重定向到该 80 端口,其他路径 / 参数等都一样

rest

org.apache.shiro.web.filter.authz

.HttpMethodPermissionFilter

rest 风格拦截器,自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串;示例 “/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete” 权限字符串进行权限匹配(所有都得匹配,isPermittedAll);

ssl

org.apache.shiro.web.filter.authz

.SslFilter

SSL 拦截器,只有请求协议是 https 才能通过;否则自动跳转会 https 端口(443);其他和 port 拦截器一样;

其他

noSessionCreation

org.apache.shiro.web.filter.session

.NoSessionCreationFilter

不创建会话拦截器,调用 subject.getSession(false) 不会有什么问题,但是如果 subject.getSession(true) 将抛出 DisabledSessionException 异常;

然后我们写相关的页面:

主页index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>

<h1>你好,<span th:text="${session.user.username}" >用户名</span> </h1>
</body>
</html>

登录页面login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>

<h1> 欢迎登录 </h1>
<form action="/loginUser" method="post">
    <input type="text" name="username"> <br>
    <input type="password" name="password"> <br>
    <input type="submit" value="提交">
</form>
</body>
</html>

到这里呢,shiro的配置基本完成了,我们可以测试一下。

先访问主页,http://localhost:8080/index  由于未登录,直接就跳到了登录界面

index.gif

我们输入用户名密码 admin  123,发现此时跳转到登录成功后的主页

login.gif

接下来访问 admin 也是成功状态

image.png

我们再换普通角色的用户来演示: demo 123

demo.gif

我们发现它跳转到了未授权界面,这个是上面的拦截器那里配置的;我们会发现以后需要动态的添加权限怎么办呢?总不可能每次添加一个权限就是修改一下代码吧?那当然不需要了,下篇我们就研究如何动态的加载权限。