SpringBoot整合Apache Shiro权限验证框架

比较常见的权限框架有两种,一种是Spring Security,另一种是Apache Shiro,两种框架各有优劣,个人感觉Shiro更容易使用,更加灵活,也更符合RABC规则,而且是java官方更推崇的安全验证框架。下面我将shiro的使用demo分享出来,能力所限,不到之处,请大家指正。

Shiro框架的核心就三个部分Subject、SecurityManager、ShiroRealm,理论内容请自行百度。

 1、准备工作

db


-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`pid` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`url` varchar(128) DEFAULT NULL,
PRIMARY KEY (`pid`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;

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

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`rid` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
PRIMARY KEY (`rid`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', 'admin');
INSERT INTO `role` VALUES ('2', 'customer');

-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`rid` int(11) DEFAULT NULL,
`pid` int(11) DEFAULT NULL,
KEY `ids_rid` (`rid`),
KEY `ids_pid` (`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES ('1', '1');
INSERT INTO `role_permission` VALUES ('1', '2');
INSERT INTO `role_permission` VALUES ('1', '3');
INSERT INTO `role_permission` VALUES ('1', '4');
INSERT INTO `role_permission` VALUES ('2', '1');
INSERT INTO `role_permission` VALUES ('2', '4');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`password` varchar(32) NOT NULL,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'admin', '123');
INSERT INTO `user` VALUES ('2', 'demo', '123');

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`uid` int(11) NOT NULL,
`rid` int(11) NOT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
KEY `ids_uid` (`uid`),
KEY `ids_rid` (`rid`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES ('1', '1', '1');
INSERT INTO `user_role` VALUES ('2', '2', '2');

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.weitian</groupId> <artifactId>shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>shiro</name> <description>Demo project for Apache Shiro</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.17.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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.25</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

application.properties

##database ##
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/test?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.jpa.database=mysql


spring.mvc.view.prefix=/pages/
spring.mvc.view.suffix=.jsp

  

2、采用jpa作为数据库持久层,将建表的任务交给框架自动完成,只需要在entity中写清楚对应关系即可。三个实体类,对应数据库中三个表(user,role,permission)

User.java

package com.weitian.model;

import lombok.Data;

import javax.persistence.*;
import java.util.List;

/**
 * Created by Administrator on 2018/11/20.
 */
@Entity
@Data
public class User {
    @Id
    @GeneratedValue
    private Integer uid;
    private String username;
    private String password;
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name="user_role",joinColumns = {@JoinColumn(name="uid")},inverseJoinColumns = {@JoinColumn(name="rid")})
    private List<Role> roleList;


}

Role.java

 

package com.weitian.model;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2018/11/20.
 */
@Entity
@Data
public class Role {
    @Id
    @GeneratedValue
    private Integer rid;
    private String name;
    @ManyToMany(fetch= FetchType.EAGER)
    @JoinTable(name="role_permission",joinColumns = {@JoinColumn(name="rid")},inverseJoinColumns = {@JoinColumn(name="pid")})
    private List<Permission> permissionList=new ArrayList<>(  );
    @ManyToMany
    @JoinTable(name="user_role",joinColumns = {@JoinColumn(name="rid")},inverseJoinColumns = {@JoinColumn(name="uid")})
    private List<User> userList=new ArrayList<>(  );


}

Permission.java

 

package com.weitian.model;

import lombok.Data;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2018/11/20.
 */
@Entity
@Data
public class Permission {
    @Id
    @GeneratedValue
    private Integer pid;
    private String name;
    private String url;
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name="role_permission",joinColumns = {@JoinColumn(name="pid")},inverseJoinColumns = {@JoinColumn(name="rid")})
    private List<Role> roleList=new ArrayList<Role>();

}

 

UserRepository

package com.weitian.repository;

import com.weitian.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * Created by Administrator on 2018/11/20.
 */

public interface UserRepository extends JpaRepository<User,Integer> {

    public User findByUsername(String userName);

}
View Code

Controller

package com.weitian.controller;

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

/**
 * Created by Administrator on 2018/11/21.
 */
@Controller
public class HomeController {
    @Autowired
    private UserService userService;


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

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


    @RequestMapping("/login")
    public String login(@RequestParam(value = "username",defaultValue = "") String username, @RequestParam(value = "password",defaultValue = "") String password){
        UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken( username,password );
        Subject subject= SecurityUtils.getSubject();
        //subject的login方法会调用自定义的验证器对登录进行验证
        try {
            subject.login( usernamePasswordToken );
            //登录成功后,将user放入session
            User user=userService.findByUserName( username );
            subject.getSession().setAttribute( "user",user );
        } catch (AuthenticationException e) {
            e.printStackTrace();
            return "login";
        }
        return "index";
    }

}
View Code

好了,至此准备工作已经完成,下面进入shiro框架核心部分

Shiro框架提供了两部分的安全核心,一部分是认证(Authentication),另一部分是验证(Authorization)。两个单词比较相似,认证的是身份信息,即登录登出使用,验证的是权限分配,即授权管理。

这两个单词第一次学习shiro框架时纠结了我好久,一方面是单词本身太相似,另一部分是被一些博客文章误导,最后查了好多源码才搞清楚具体的细节。

这一部分我是这样理解的:

用户来访问网站资源,我们使用shiro框架对其进行访问控制及权限管理,但是shiro框架如何得知这个用户的登录名是否正确,登录成功后又拥有何种的权限呢?所以我们需要获取这部分信息,并告诉shiro,具体做法是:继承AuthorizingRealm类,并重写其中的两个方法:

package com.weitian.auth;

import com.weitian.ResultRnum.ResultEnum;
import com.weitian.entity.Permission;
import com.weitian.entity.Role;
import com.weitian.entity.User;
import com.weitian.exception.ResultException;
import com.weitian.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.List;

/**
* Created by Administrator on 2018/11/21.
*/
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/*
该类用于保存身份认证信息,即登录信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
/*
token中保存了用户登录时的信息,查源码可以看出token.getPrincipal()方法返回用户名
*/
String username=(String)token.getPrincipal();

User user=userService.findByUserName( username );
if(null==user){
throw new ResultException( ResultEnum.USER_NOT_EXISTS);
}
/*
将从数据库中查询得到的用户信息保存在shiro框架的AuthenticationInfo中,准备与token中的用户登录信息进行校验
*/
SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo( user.getUsername(),user.getPassword(),this.getName() );
return authenticationInfo;
}
//权限信息
//该类用户保存登录用户的权限信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

/*用户登录后,从session中取出用户权限、角色信息,填充到AuthorizationInfo对象中,进行后续验证*/
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo( );
String username=(String)principals.getPrimaryPrincipal();
User user=userService.findByUserName( username );

List<Role> roleList=user.getRoleList();
for(Role role:roleList){
//保存用户的角色信息
authorizationInfo.addRole( role.getName() );
List<Permission> permissionList=role.getPermissionList();
for(Permission permission:permissionList){
//保存用户的权限信息
authorizationInfo.addStringPermission( permission.getName() );
}
}
return authorizationInfo;
}


}

当用户登录时,我们以何种方式校验用户呢?可以直接将用户的登录信息与数据库的信息进行比较,也可以使用自定义的算法进行校验,Shiro框架提供了不同的校验方式,常用的有SimpleCredentialsMatcher类,HashedCredentialsMatcher(哈希散列校验),

 

package com.weitian.auth;

import com.weitian.ResultRnum.ResultEnum;
import com.weitian.exception.ResultException;
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;

/**
 * Created by Administrator on 2018/11/21.
 */
public class CredentialMatcher extends SimpleCredentialsMatcher{

    /*
    验证器,在这里可以自定义登录校验规则
     */
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {

        UsernamePasswordToken usernamePasswordToken=(UsernamePasswordToken) token;
        String loginUsername=usernamePasswordToken.getUsername();
        String loginPassword=new String(usernamePasswordToken.getPassword());


        String dbUserName=(String)info.getPrincipals().getPrimaryPrincipal();
        String dbPassword=(String)info.getCredentials();

        if(!(this.equals( loginUsername,dbUserName ) && this.equals( loginPassword,dbPassword ))){
            throw new ResultException( ResultEnum.LOGIN_IS_ERROR );
        }
        return true;
    }
}

接下来,将我们上面定义的两个类与shiro框架结合在一起

package com.weitian.config;

import com.weitian.auth.CredentialMatcher;
import com.weitian.auth.ShiroRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

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

/**
 * Created by Administrator on 2018/11/21.
 */
@Configuration
public class ShiroConfig {

    /*
    4、根据业务需求配置授权过滤器链
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager){
        ShiroFilterFactoryBean factoryBean=new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager( securityManager );

        //设置登录页面
        factoryBean.setLoginUrl( "/login" );
        //设置登录成功后的跳转页面
        factoryBean.setSuccessUrl( "/index" );
        //设置无权访问的跳转页面
        factoryBean.setUnauthorizedUrl( "/unauthc" );


        //配置校验规则
        /*
        校验规则为枚举类型,常用的有:
        (1)anon:匿名过滤器,表示通过了url配置的资源都可以访问,例:“/statics/**=anon”表示statics目录下所有资源都能访问
        (2)authc:基于表单的过滤器,表示通过了url配置的资源需要登录验证,否则跳转到登录,例:“/unauthor.jsp=authc”如果用户没有登录访问unauthor.jsp则直接跳转到登录
        (3)authcBasic:Basic的身份验证过滤器,表示通过了url配置的资源会提示身份验证,例:“/welcom.jsp=authcBasic”访问welcom.jsp时会弹出身份验证框
        (4)perms:权限过滤器,表示访问通过了url配置的资源会检查相应权限,例:“/statics/**=perms["user:add:*,user:modify:*"]“表示访问statics目录下的资源时只有新增和修改的权限
        (5)port:端口过滤器,表示会验证通过了url配置的资源的请求的端口号,例:“/port.jsp=port[8088]”访问port.jsp时端口号不是8088会提示错误
        (6)rest:restful类型过滤器,表示会对通过了url配置的资源进行restful风格检查,例:“/welcom=rest[user:create]”表示通过restful访问welcom资源时只有新增权限
        (7)roles:角色过滤器,表示访问通过了url配置的资源会检查是否拥有该角色,例:“/welcom.jsp=roles[admin]”表示访问welcom.jsp页面时会检查是否拥有admin角色
        (8)ssl:ssl过滤器,表示通过了url配置的资源只能通过https协议访问,例:“/welcom.jsp=ssl”表示访问welcom.jsp页面如果请求协议不是https会提示错误
        (9)user:用户过滤器,表示可以使用登录验证/记住我的方式访问通过了url配置的资源,例:“/welcom.jsp=user”表示访问welcom.jsp页面可以通过登录验证或使用记住我后访问,否则直接跳转到登录
        (10)logout:退出拦截器,表示执行logout方法后,跳转到通过了url配置的资源,例:“/logout.jsp=logout”表示执行了logout方法后直接跳转到logout.jsp页面
            
            过滤器分类:
        (1)认证过滤器:anon、authcBasic、auchc、user、logout

        (2)授权过滤器:perms、roles、ssl、rest、port
        
            URL模糊规则:

        (1)“?”:匹配一个字符,如”/admin?”,将匹配“ /admin1”、“/admin2”,但不匹配“/admin”
        
        (2)“*”:匹配零个或多个字符串,如“/admin*”,将匹配“ /admin”、“/admin123”,但不匹配“/admin/1”
        
        (3)“**”:匹配路径中的零个或多个路径,如“/admin/**”,将匹配“/admin/a”、“/admin/a/b”
        
         */
        Map<String,String> filterChianDefinitionMap=new HashMap<String,String>(  );


        filterChianDefinitionMap.put( "/index","authc" );

        filterChianDefinitionMap.put( "/login","anon" );
        filterChianDefinitionMap.put( "/**","user" );
        filterChianDefinitionMap.put( "/druid/**","anon" );

        filterChianDefinitionMap.put("/admin", "roles[admin]");
        filterChianDefinitionMap.put("/authc/renewable", "perms[Create,Update]");
        filterChianDefinitionMap.put("/authc/removable/*", "perms[Delete]");

        
        factoryBean.setFilterChainDefinitionMap( filterChianDefinitionMap );
        return factoryBean;
    }

    /*
    3、将shirorealm对象交给shiro框架的SecurityManager管理
    SecurityManager对象,并将shirorealm纳入其中管理
     */
    @Bean("securityManager")
    public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm shiroRealm){
        DefaultWebSecurityManager webSecurityManager=new DefaultWebSecurityManager( shiroRealm );
        return webSecurityManager;
    }
    /*
    2、告诉shirorealm,我们的校验规则
    生成ShiroRealm对象,并为该对象设置自定义的校验规则
     */
    @Bean("shiroRealm")
    public ShiroRealm shiroRealm(@Qualifier("credentialMatcher") CredentialsMatcher credentialsMatcher){
        ShiroRealm shiroRealm=new ShiroRealm();
        shiroRealm.setCredentialsMatcher( credentialsMatcher );
        //使用shiro缓存管理
        shiroRealm.setCacheManager( new MemoryConstrainedCacheManager() );
        return shiroRealm;
    }

    /*
        1、由spring生成我们自己的校验规则对象
     */
    @Bean("credentialMatcher")
    public CredentialMatcher credentialMatcher(){
        CredentialMatcher credentialMatcher=new CredentialMatcher();
        return credentialMatcher;
    }
}

这样,我们对在Springboot下如何使用Shiro的介绍就告一段落,有希望看到后续更加深入文章的小伙伴欢迎踊跃马克。另外,大部分代码我已经在文章中提供如果需要源码的小伙伴请自行下载

https://github.com/phoenixyouda/shiro.git

 

posted @ 2018-11-21 17:21  郭老头  阅读(635)  评论(0编辑  收藏  举报