springboot2.0集成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>

首先建立一个简单的springboot项目,这里数据持久化方式使用JPA,项目文件结构如下所示:

下面列出各个类的详细代码:

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 = 1L;

    @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;
/**
 * 系统角色实体类;
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
@Entity
public class SysRole implements Serializable {

    private static final long serialVersionUID = 1L;

    @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.*;

/**
 * 权限实体类;
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
@Entity
public class SysPermission implements Serializable{
    private static final long serialVersionUID = 1L;

    @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 lang="en">
<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;

/**
 * @author Angel(QQ:412887952)
 * @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;
    }
}

配置完成后可以顺利使用。

总结一下springboot整合shiro的流程:

(1)添加对应依赖

(2)添加shiro验证过程中所需要的bean、方法、持久层信息。

(3)重新自定义的继承自AuthorizingRealm的Realm。

  • AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)认证方法流程
    • 从token中取出用户信息。
    • 根据获得的用户信息从数据库中查找user对象。
    • 进行判断,若无此用户,则返回null,有的话则将其交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,密码匹配的方式可以自定义实现。。
    • 返回authenticatingRealm。
  • AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)权限验证方法流程
    • 从principalCollection中获取用户信息。
    • 根据用户信息获取权限信息,将权限信息放入authorizationInfo中(shiro会自动进行权限验证管理)。
    • 返回authorizationInfo。

(4)将shiro配置到项目中。

  • 由于我们对密码进行了加密,所以需要创建凭证匹配器CredentialsMatcher,用来匹配授权认证的时候的密码。
  • 创建realm并将凭证匹配器CredentialsMatcher加入到realm中。
  • 创建SecurityManager并将realm设到其中
  • 创建ShiroFilterFactoryBean,并将SecurityManager注入其中。
  • 创建AuthorizationAttributeSourceAdvisor 开启AOP注解支持

 至此,shiro已经简单的配置完毕。

后续发现问题会再次修改。

详细步骤请见:http://412887952-qq-com.iteye.com/blog/2299732

猜你喜欢

转载自my.oschina.net/mrfu/blog/1634468