springboot集成shiro需要怎么做?

TheDisguiser 2020-07-21 11:21:52 java常见问答 3504

对于springboot集成shiro小伙伴们知道要实现吗?今天我们就来了解下该如何在springboot集成shiro。

首先我们需要创建一个简单的springboot项目,如下:

springboot集成shiro

然后引入所需要的pom依赖:

<!-- spring 集成 shiro -->
<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>

接下来就是各个类的详细编码:

UserInfo

package com.slf.firstappdemo.framework.shiro.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
@Entity
public class UserInfo implements Serializable
{
    private static final long serialVersionUID = 1 L;
    @Id @GeneratedValue
    private long id; //用户ID
    @Column(unique = true)
    private String username; //账号
    private String name; //名称
    private String password; //密码
    private String salt; //加密密码的盐,在这里salt主要是用来进行密码加密的,当然也可以使用明文进行编码测试,实际开发中还是建议密码进行加密。
    private byte state; //用户状态:1:创建未认证(比如没有激活,没有输入验证码等) 等待验证的用户,1:正常状态,2:用户被锁定。
    @ManyToMany(fetch = FetchType.EAGER) //立即从数据库进行加载数据
    @JoinTable(name = "SysUserrole", joinColumns = {
            @JoinColumn(name = "uid")
        }
        , inverseJoinColumns = {
            @JoinColumn(name = "roleId")
        }
    )
    private List < SysRole > roleList;
    //getter and setter
    /**
     * 密码盐.
     * @return
     */
    public String getCredentialsSalt()
    {
        return this.username + this.salt;
    }
    //toString
}

SysRole

package com.slf.firstappdemo.framework.shiro.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
/**
 * 系统角色实体类;
 * @version v.0.1
 */
@Entity
public class SysRole implements Serializable
{
    private static final long serialVersionUID = 1 L;
    @Id @GeneratedValue
    private Long id; //编号
    private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
    private String description; // 角色描述,UI界面显示使用
    private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户
    //角色 -- 权限关系:多对多关系;
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "SysRolePermission", joinColumns = {
        @JoinColumn(name = "roleId")
    }, inverseJoinColumns = {
        @JoinColumn(name = "permissionId")
    })
    private List < SysPermission > permissions;
    // 用户 - 角色关系定义;
    @ManyToMany
    @JoinTable(name = "SysUserRole", joinColumns = {
        @JoinColumn(name = "roleId")
    }, inverseJoinColumns = {
        @JoinColumn(name = "uid")
    })
    private List < UserInfo > userInfos; // 一个角色对应多个用户
    //getter and setter
    //toString
}

SysPermission

package com.slf.firstappdemo.framework.shiro.domain;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
/**
 * 权限实体类;
 * @version v.0.1
 */
@Entity
public class SysPermission implements Serializable
{
    private static final long serialVersionUID = 1 L;
    @Id @GeneratedValue
    private long id; //主键.
    private String name; //名称.
    @Column(columnDefinition = "enum('menu','button')")
    private String resourceType; //资源类型,[menu|button]
    private String url; //资源路径.
    private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
    private Long parentId; //父编号
    private String parentIds; //父编号列表
    private Boolean available = Boolean.FALSE;
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "SysRolePermission", joinColumns = {
        @JoinColumn(name = "permissionId")
    }, inverseJoinColumns = {
        @JoinColumn(name = "roleId")
    })
    private List < SysRole > roles;
    //getter and setter
    //toString()
}

UserInfoRepository

package com.slf.firstappdemo.framework.shiro.repository;
import com.slf.firstappdemo.framework.shiro.domain.UserInfo;
import org.springframework.data.repository.CrudRepository;
/**
 *@Author:Flm
 *@Description:UserInfo持久化类
 *@Date:17:14 2018/3/13
 */
public interface UserInfoRepository extends CrudRepository < UserInfo, Long >
{
    //通过username查找用户信息
    public UserInfo findByUsername(String username);
}

UserInfoServiceImpl

import com.slf.firstappdemo.framework.shiro.domain.UserInfo;
import com.slf.firstappdemo.framework.shiro.repository.UserInfoRepository;
import com.slf.firstappdemo.framework.shiro.service.UserInfoService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserInfoServiceImpl implements UserInfoService
{
    @Resource
    private UserInfoRepository userInfoRepository;
    @Override
    public UserInfo findByUserName(String username)
    {
        System.out.println("UserInfoServiceImpl.findByUserName()");
        return userInfoRepository.findByUsername(username);
    }
}

Controller(接口省略)

package com.slf.firstappdemo.framework.shiro.controller;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Controller
@RequestMapping("/shiro")
public class ShiroController
{
    @RequestMapping(
    {
        "/"
        , "/index"
    })
    public String index()
    {
        return "/framwork/shiro/index";
    }
    @RequestMapping(value = "/login", method = RequestMethod.GET) //跳转页面
    public String login()
    {
        return "/framwork/shiro/login";
    }
    @RequestMapping(value = "/login", method = RequestMethod.POST) //处理登录
    public String doLogin(HttpServletRequest request, Map < String, Object > map)
    {
        System.out.println("ShiroController.doLogin()");
        //登录失败从request中获取shiro处理的异常信息
        //shiroLoginFailure:就是shiro异常类的全名
        String exception = (String) request.getAttribute("shiroLoginFailure");
        System.out.println("exception=========" + exception);
        String msg = "";
        if (exception != null)
        {
            if (UnknownAccountException.class.getName()
                .equals(exception))
            {
                System.out.println("UnknownAccountException -- > 账号不存在:");
                msg = "UnknownAccountException -- > 账号不存在:";
            }
            else if (IncorrectCredentialsException.class.getName()
                .equals(exception))
            {
                System.out.println("IncorrectCredentialsException -- > 密码不正确:");
                msg = "IncorrectCredentialsException -- > 密码不正确:";
            }
            else if ("kaptchaValidateFailed".equals(exception))
            {
                System.out.println("kaptchaValidateFailed -- > 验证码错误");
                msg = "kaptchaValidateFailed -- > 验证码错误";
            }
            else
            {
                msg = "else >> " + exception;
                System.out.println("else -- >" + exception);
            }
        }
        map.put("msg", msg);
        return "/framwork/shiro/login";
    }
    @RequestMapping("/userList")
    @RequiresPermissions("userInfo:view")
    public String userInfo()
    {
        return "/framwork/shiro/userinfo";
    }
    @RequestMapping("/userAdd")
    @RequiresPermissions("userInfo:add")
    public String userAdd()
    {
        return "/framwork/shiro/userinfoAdd";
    }
}

login.html(其他页面省略)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
错误信息:<h4 th:text="${msg}"></h4>
<form action="/shiro/login" method="post">
    <p>账号:<input type="text" name="username" value="admin"/></p>
    <p>密码:<input type="text" name="password" value="123456"/></p>
    <p><input type="submit" value="登录"/></p>
</form>
</body>
</html>

自定义实现Realm:

此类为身份验证的核心类,需要继承AuthorizingRealm类,且实现两个方法

(1)doGetAuthenticationInfo(AuthenticationToken authenticationToken) 获取认证信息,用来验证身份信息。

(2)doGetAuthorizationInfo(PrincipalCollection principalCollection) 获取授权信息,用来进行权限验证。

package com.slf.firstappdemo.framework.shiro.realm;
import com.slf.firstappdemo.framework.shiro.domain.SysPermission;
import com.slf.firstappdemo.framework.shiro.domain.SysRole;
import com.slf.firstappdemo.framework.shiro.domain.UserInfo;
import com.slf.firstappdemo.framework.shiro.service.UserInfoService;
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.apache.shiro.util.ByteSource;
import javax.annotation.Resource;
/**
 * @Author:Flm
 * @Description:身份验证核心类
 * @Date:17:21 2018/3/13
 */
public class MyShiroRealm extends AuthorizingRealm
{
    @Resource
    private UserInfoService userInfoService;
    /**
     * @Author:Flm
     * @Description:认证信息(身份验证)
     * @Date:17:23 2018/3/13
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException
    {
        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
        //获取用户的输入账号
        String username = (String) authenticationToken.getPrincipal();
        System.out.println(authenticationToken.getCredentials());
        //通过username从数据库中查找user对象
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro也是有时间间隔机制,2分钟内不会重复执行该方法
        UserInfo userInfo = userInfoService.findByUserName(username);
        System.out.println("----->>userInfo=" + userInfo);
        if (userInfo == null)
        {
            return null;
        }
        /**
         * 获取权限信息:这里没有进行实现
         * 请自行根据UserInfo,Role,Permission进行实现
         * 获取之后可以在前端for循环显示所有链接
         */
        //userInfo.setPermissions(userService.findPermissions(user));
        //账号判断
        //加密方式
        //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, //用户名
            userInfo.getPassword(), //密码
            ByteSource.Util.bytes(userInfo.getCredentialsSalt()), //salt=username+salt
            getName() //realm name
        );
        //明文,若存在,则将此用户存放到登录认证info中,无需自己做密码对比,shiro会为我们进行密码对比校验
        //SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo,userInfo.getPassword(),getName());
        return authenticationInfo;
    }
    /**
     * 此方法调用  hasRole,hasPermission的时候才会进行回调.
     *
     * 权限信息.(授权):
     * 1、如果用户正常退出,缓存自动清空;
     * 2、如果用户非正常退出,缓存自动清空;
     * 3、如果我们修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。
     * (需要手动编程进行实现;放在service进行调用)
     * 在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例,
     * 调用clearCached方法;
     * :Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)
    {
        /*
         * 当没有使用缓存的时候,不断刷新页面的话,这个代码会不断执行,
         * 当其实没有必要每次都重新设置权限信息,所以我们需要放到缓存中进行管理;
         * 当放到缓存中时,这样的话,doGetAuthorizationInfo就只会执行一次了,
         * 缓存过期之后会再次执行。
         */
        System.out.println("权限配置-----》MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        UserInfo userInfo = (UserInfo) principalCollection.getPrimaryPrincipal();
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        //     UserInfo userInfo = userInfoService.findByUsername(username)
        //权限单个添加;
        // 或者按下面这样添加
        //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色
        //     authorizationInfo.addRole("admin");
        //添加权限
        //     authorizationInfo.addStringPermission("userInfo:query");
        //在认证成功之后返回.
        //设置角色信息.
        //支持 Set集合,
        //用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的四行可以不要
        //        List<Role> roleList=user.getRoleList();
        //        for (Role role : roleList) {
        //            info.addStringPermissions(role.getPermissionsName());
        //        }
        for (SysRole role: userInfo.getRoleList())
        {
            simpleAuthorizationInfo.addRole(role.getRole());
            for (SysPermission p: role.getPermissions())
            {
                simpleAuthorizationInfo.addStringPermission(p.getPermission());
                System.out.println("user拥有权限:" + p.getPermission());
            }
        }
        return simpleAuthorizationInfo;
    }
}

实现了自定义Realm之后,就需要把shiro配置到spring中:

package com.slf.firstappdemo.framework.shiro.config;
import com.slf.firstappdemo.framework.shiro.realm.MyShiroRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * @version v.0.1
 * @Author:Flm
 * @Description:shiro config
 * @Date:16:15 2018/3/13
 * Shiro 配置
 * 
 * Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。
 * 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
 */
@Configuration
public class ShiroConfig
{
    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * 所以我们需要修改下doGetAuthenticationInfo中的代码;
     * )
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher()
    {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5"); //散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2); //散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }
    /**
     * 身份认证realm;
     * (这个需要自己写,账号密码校验;权限等)
     *
     * @return
     */
    @Bean
    public MyShiroRealm myShiroRealm()
    {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }
    @Bean
    public SecurityManager securityManager()
    {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置realm
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }
    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     * <p>
     * Filter Chain定义说明
     * 1、一个URL可以配置多个Filter,使用逗号分隔
     * 2、当设置多个过滤器时,全部验证通过,才视为通过
     * 3、部分过滤器可指定参数,如perms,roles
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager)
    {
        System.out.println("ShiroConfiguration.shiroFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //必须设置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //拦截器
        Map < String, String > filterChainDefinitioinMap = new LinkedHashMap < String, String > ();
        //配置退出过滤器,其中的具体的退出代码shiro已经实现
        filterChainDefinitioinMap.put("/logout", "logout");
        //过滤连定义,从上向下顺序执行,一般将/**放在最为下边 !!!!!
        //authc:所有的url都必须认证通过才可以访问;anon:所有的url都可以匿名访问
        filterChainDefinitioinMap.put("/shiro/**", "authc");
        //如果不设置默认会自动寻找web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/shiro/login"); //此处应是url并非静态资源位置
        //登陆成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/shiro/index");
        //未授权页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitioinMap);
        return shiroFilterFactoryBean;
    }
    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager)
    {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

这样我们就全部配置成功可以尽情的使用它了。

以上就是本篇文章的所有内容,更多java项目中常见问题及解决方法,可以关注我们网站了解具体。

推荐阅读:

springboot部署到服务器流程有哪些?

springboot框架的优点是什么? springboot比spring的优势如何?

springcloud和springboot的区别是什么?