Spring Boot 入门之路(17)--- Spring Boot 与 Shiro 的集成

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Geffin/article/details/100566230

本文将不会介绍 Shiro 的原理,而是会直接说明 Spring Boot 如何与 Shiro 集成,若对 Shiro 还不了解的同学可以看我之前的文章:Shiro 关于认证与授权功能的实现

1 引入依赖

		<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
   			<groupId>org.apache.shiro</groupId>
   			<artifactId>shiro-ehcache</artifactId>
   			<version>1.4.0</version>
		</dependency>

2 创建 pojo

我们创建三个类,分别模拟数据库中的用户表,角色表与权限表

用户类

package edu.szu.test.entity;

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

public class User {

	private Integer uid;
    private String username;
    private String password;
    private Set<Role> roles = new HashSet<Role>();
    
	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;
	}
}

角色类

package edu.szu.test.entity;

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

public class Role {
	
	private Integer rid;
    private String rname;
    private Set<Permission> permissions = new HashSet<Permission>();
    
	public Integer getRid() {
		return rid;
	}
	public void setRid(Integer rid) {
		this.rid = rid;
	}
	public String getRname() {
		return rname;
	}
	public void setRname(String rname) {
		this.rname = rname;
	}
	public Set<Permission> getPermissions() {
		return permissions;
	}
	public void setPermissions(Set<Permission> permissions) {
		this.permissions = permissions;
	}   
}

权限类

package edu.szu.test.entity;

public class Permission {
	
	private Integer pid;
    private String name;
	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 Permission(Integer pid, String name) {
		super();
		this.pid = pid;
		this.name = name;
	}
}

3 创建服务类

我们创建服务类来模拟数据库的查询,这里我直接把数据写死,返回了一条自定义的数据

package edu.szu.test.service;

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

import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
import org.springframework.stereotype.Service;

import edu.szu.test.entity.Permission;
import edu.szu.test.entity.Role;
import edu.szu.test.entity.User;

@Service
public class UserService {

	//根据用户名查询返回用户
	//我这里使用静态数据代替数据库查询
	public User getUserByUsername(String username) {
		User user = new User();
		Set<Role> set = new HashSet<Role>();
		Role role = new Role();
		Set<Permission> set1 = new HashSet<Permission>();
		
		set1.add(new Permission(1, "edit"));
		role.setPermissions(set1);
		role.setRid(1);
		role.setRname("admin");
		
		set.add(role);
		
		user.setRoles(set);
		user.setPassword(new SimpleHash("MD5","123456",ByteSource.Util.bytes("Ling"),1024).toHex());//密码明文为123456,盐值为用户名
		user.setUid(1);
		user.setUsername(username);
		return user;
	}
}

4 新建一个 Realm

Realm 用于实现认证与授权功能

package edu.szu.test.config;

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

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.authc.UsernamePasswordToken;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import edu.szu.test.entity.Permission;
import edu.szu.test.entity.Role;
import edu.szu.test.entity.User;
import edu.szu.test.service.UserService;

public class MyRealm extends AuthorizingRealm{

	@Autowired
	UserService userService;
	
	//授权
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// 获取User
	    User user = (User) principals.getPrimaryPrincipal();
	    List<String> permissionList = new ArrayList<String>();
	    List<String> roleNameList = new ArrayList<String>();
	    Set<Role> roleSet = user.getRoles();
	    if (roleSet != null){
	        for (Role role : roleSet){
	            roleNameList.add(role.getRname());
	            Set<Permission> permissionSet = role.getPermissions();
	            if (permissionSet != null){
	                for (Permission permission : permissionSet){
	                    permissionList.add(permission.getName());
	                }
	            }
	        }
	    }

	    // 把角色和权限放入info中
	    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
	    // 权限设定
	    info.addStringPermissions(permissionList);
	    // 角色设定
	    info.addRoles(roleNameList);
	    return info;
	}

	//认证
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
		AuthenticationInfo info = null;
        //获取用户名
        String username = upToken.getUsername();
        //查询数据库.我这里直接写死
        User user = userService.getUserByUsername(username);

        if(user != null) {
            //获取盐值,这里为我们的用户名
            ByteSource salt = ByteSource.Util.bytes("Ling");
            String realmName = getName();
            // 将用户,密码,盐值,realmName实例化到SimpleAuthenticationInfo中交给Shiro来管理
            info = new SimpleAuthenticationInfo(user,user.getPassword(),salt,realmName);
        }else {
            // 如果没有查询到,抛出一个异常
            throw new AuthenticationException();
        }
        return info;
	}	
}

5 实现 Shiro 配置类

/**
 * Shiro配置类
 * @author 30309
 *
 */
@Configuration
public class ShiroConfig {
	
	//设置对应的过滤条件和跳转条件
    @Bean 
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager); 
        //未登陆时,统一跳转至login.jsp
        shiroFilterFactoryBean.setLoginUrl("/login");
        //成功登陆后,统一跳转至index.jsp
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //访问没有权限访问的界面,统一跳转至out.jsp
        shiroFilterFactoryBean.setUnauthorizedUrl("/out");
        //设置权限
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // index界面需要鉴权
        //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
        filterChainDefinitionMap.put("/index", "authc");
        // login、loginUser不需要验证
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/loginUser", "anon");
        // admin需要角色admin才能访问
        filterChainDefinitionMap.put("/admin", "roles[admin]");
        // edit需要权限edit才能访问
        filterChainDefinitionMap.put("/edit", "perms[edit]");
        filterChainDefinitionMap.put("/**", "user");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

	//将自己的验证方式加入容器
    @Bean(name = "myRealm")
    public MyRealm myRealm(HashedCredentialsMatcher matcher) {
        MyRealm myRealm = new MyRealm();
        myRealm.setCredentialsMatcher(matcher);
        return myRealm;
    }

    //Realm的管理认证
    @Bean
    public DefaultWebSecurityManager securityManager(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm(matcher));
        return securityManager;
    }
    
    //密码匹配凭证管理器
    @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //采用MD5方式加密
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        //设置加密次数
        hashedCredentialsMatcher.setHashIterations(1024);
        return hashedCredentialsMatcher;
    }
}

6 创建控制器

package edu.szu.test.controller;

import javax.servlet.http.HttpSession;

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 edu.szu.test.entity.User;

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

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

    //退出登录
    @RequestMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        if (subject != null){
            subject.logout();
        }
        return "login";
    }

    @RequestMapping("/admin")
    @ResponseBody
    public String admin(){
        return "admin页面可以成功被访问";
    }

    @RequestMapping("/edit")
    @ResponseBody
    public String edit(){
        return "edit页面可以成功被访问";
    }

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

7 测试

我们之前设置的用户是拥有 admin 角色与 edit 权限的,即该用户是可以访问 /admin 和 /edit 的。

我们先访问 http://localhost:8080/admin,发现会跳转至登录页面
在这里插入图片描述
输入账号密码,会跳转至 index.jsp
在这里插入图片描述
然后我们再访问 http://localhost:8080/admin,发现可以访问该页面
在这里插入图片描述
证明我们已经登录成功。

8 使用注解控制鉴权授权

我们修改一下 Shiro 配置类,加上对注解的使用

	//加入注解的使用
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    //加入注解的使用
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager(matcher));
        return authorizationAttributeSourceAdvisor;
    }

    //加入注解的使用
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

再修改一下过滤条件

	//设置对应的过滤条件和跳转条件
    @Bean 
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager); 
        //未登陆时,统一跳转至login.jsp
        shiroFilterFactoryBean.setLoginUrl("/login");
        //成功登陆后,统一跳转至index.jsp
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //访问没有权限访问的界面,统一跳转至out.jsp
        shiroFilterFactoryBean.setUnauthorizedUrl("/out");
        //设置权限
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        /**
        // index界面需要鉴权
        //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
        filterChainDefinitionMap.put("/index", "authc");
        // login、loginUser不需要验证
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/loginUser", "anon");
        // admin需要角色admin才能访问
        filterChainDefinitionMap.put("/admin", "roles[admin]");
        // edit需要权限edit才能访问
        filterChainDefinitionMap.put("/edit", "perms[edit]");
        **/
        //所有路径均可匿名访问
        filterChainDefinitionMap.put("/**", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

然后我们修改下这个用户的信息,取消 edit 权限,保持原来的 admin 角色不变

再修改一下控制器

@Controller
public class TestController{
	
	//没有加上@RequiresAuthentication注解
	//可以匿名访问
	@RequestMapping("/login")
    public String login(){
        return "login";
    }

	//已登录用户才能访问
	// 如果用户未登录调用该接口,会抛出UnauthenticatedException
	@RequiresAuthentication
    @RequestMapping("/index")
    public String index(){
        return "index";
    }

    //退出登录
	@RequiresAuthentication
    @RequestMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        if (subject != null){
            subject.logout();
        }
        return "login";
    }

	//要求登录的用户具有admin角色才能访问
    //我们设置的用户有这个角色,所以可以访问
    //如果没有登录,会抛出UnauthenticatedException
	@RequiresRoles("admin")
    @RequestMapping("/admin")
    @ResponseBody
    public String admin(){
        return "admin页面可以成功被访问";
    }

    //要求登录的用户具有edit权限才能访问
    //我们设置的用户并没有这个权限,所以不能访问
    //注意:如果没有登录,会抛出UnauthenticatedException
    //如果登录了,但是没有这个权限,会报错UnauthorizedException
    @RequiresPermissions("edit")
    @RequestMapping("/edit")
    @ResponseBody
    public String edit(){
        return "edit页面可以成功被访问";
    }

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

进入登录页面
在这里插入图片描述
然后访问 /admin
在这里插入图片描述
访问 /edit 失败(没有这个权限)
在这里插入图片描述

参考:
SpringBoot集成Shiro思路以及代码过程

Shiro用starter方式优雅整合到SpringBoot中

猜你喜欢

转载自blog.csdn.net/Geffin/article/details/100566230