spring boot整合shiro

spring boot使用shiro做登录认证,权限验证,会话管理操作

1.需要三张表-用户表(user),角色表(role),权限表(permission)

spring boot整合shiro_第1张图片spring boot整合shiro_第2张图片

建表语句


SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL COMMENT '角色ID',
  `permission` varchar(63) DEFAULT NULL COMMENT '权限',
  `add_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=546 DEFAULT CHARSET=utf8mb4 COMMENT='权限表';

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(63) NOT NULL COMMENT '角色名称',
  `desc` varchar(1023) DEFAULT NULL COMMENT '角色描述',
  `enabled` tinyint(1) DEFAULT '1' COMMENT '是否启用',
  `add_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(63) NOT NULL COMMENT '用户名称',
  `password` varchar(63) NOT NULL DEFAULT '' COMMENT '用户密码',
  `gender` tinyint(3) NOT NULL DEFAULT '0' COMMENT '性别:0 未知, 1男, 1 女',
  `birthday` date DEFAULT NULL COMMENT '生日',
  `status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '0 可用, 1 禁用, 2 注销',
  `add_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_name` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

导包

pom.xml


            org.apache.shiro
            shiro-spring
            1.4.0
        

 

配置

1.自定义realm认证实现登录认证和授权操作,此类需要继承AuthorizingRealm

注:密码使用了md5加密,使用username进行加盐。md5配置在下方ShiroConfiguration配置类中

package com.zcb.minimalladminapi.realm;

import com.zcb.minimalldb.domain.Admin;
import com.zcb.minimalldb.domain.User;
import com.zcb.minimalldb.service.IAdminService;
import com.zcb.minimalldb.service.IPermissionService;
import com.zcb.minimalldb.service.IRolesService;
import com.zcb.minimalldb.service.IUserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
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 org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

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

@Component
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private IAdminService adminService;

    @Autowired
    private IRolesService rolesService;

    @Autowired
    private IPermissionService permissionService;
    /**
     * 授权方法
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        if (principals == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
        }
        /**
         * 注意principals.getPrimaryPrincipal()对应
         * new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName())的第一个参数
         */
        String userName = (String) getAvailablePrincipal(principals);
        System.out.println("-----------"+userName);
        List adminList = adminService.findByUsername(userName);
        if (adminList == null || adminList.size() == 0) {
            throw new UnknownAccountException("找不到" + userName + "的账号信息");
        } else if (adminList.size() > 1) {
            throw new UnknownAccountException(userName + "对应多个账号信息");
        }
        Admin admin = adminList.get(0);

        Integer[] roleIds = admin.getRoleIds();
        Set roles = rolesService.queryByIds(roleIds);
        Set permissions = permissionService.queryByIds(roleIds);

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //为当前用户赋予对应角色和权限
        info.setRoles(roles);
        info.setStringPermissions(permissions);
        System.out.println("info=="+roles + "--"+permissions);
        return info;
    }

    /**
     * 认证方法
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String userName = upToken.getUsername();
        String password = new String(upToken.getPassword());
        if (StringUtils.isEmpty(userName)) {
            throw new AccountException("用户名不能为空");
        }
        if (StringUtils.isEmpty(password)) {
            throw new AccountException("密码不能为空");
        }
        //用户名
       // String userName = (String) token.getPrincipal();
        //从数据库中查找用户信息
        List adminList = adminService.findByUsername(userName);
        if (adminList == null || adminList.size() == 0) {
            throw new UnknownAccountException("找不到" + userName + "的账号信息");
        } else if (adminList.size() > 1) {
            throw new UnknownAccountException(userName + "对应多个账号信息");
        }
        Admin admin = adminList.get(0);
        ByteSource credentialsSalt = ByteSource.Util.bytes(admin.getUsername()); //使用userName加盐
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(admin.getUsername(), admin.getPassword(), credentialsSalt, getName());
        return info;
    }
    public static void main(String[] args) {
        Md5Hash md5Hash = new Md5Hash("123456","admin",1024);
        //Object md5Pwd = new SimpleHash("MD5","123456","084015124",1024);
        System.out.println(md5Hash);
    }
}

 2.shiro配置。

注意@Configuration和@Bean注解的使用

  1. 配置加密方式md5
  2. 加入自定义的认证方式
  3. 配置会话管理(用来认证token--token是sessionid)
  4. 配置安全管理员
  5. 配置过滤方式
package com.zcb.minimalladminapi.config;

import com.zcb.minimalladminapi.realm.MyRealm;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.session.mgt.SessionManager;
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.HashMap;
import java.util.Map;

/**
 * shiro过滤配置
 */
@Configuration
public class ShiroConfiguration {
    private static final Logger LOGGER = LogManager.getLogger();

    /**
     * 加密方式
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(1024);
        return hashedCredentialsMatcher;
    }
    //将自己的验证方式加入容器
    @Bean
    public MyRealm myShiroRealm() {
        MyRealm myRealm = new MyRealm();
        myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        LOGGER.info("md5");
        return myRealm;
    }
    //会话管理
    @Bean
    public SessionManager sessionManager() {
        AdminSessionManager adminSessionManager = new AdminSessionManager();
        return  adminSessionManager;
    }

    //权限管理,配置主要是Realm的管理认证

    /**
     * 安全管理员
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        LOGGER.info("start securityManager");
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        securityManager.setSessionManager(sessionManager());

        return securityManager;
    }


    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        LOGGER.info("config shiro filter");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map map = new HashMap();
        //登出
        map.put("/logout","logout");
        //登录 不验证
        map.put("/admin/auth/login", "anon");


        //
        map.put("/**","anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        //登录
        shiroFilterFactoryBean.setLoginUrl("/unauth"); //跳转至登录接口
        //首页
        //shiroFilterFactoryBean.setSuccessUrl("/index");
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        return shiroFilterFactoryBean;
    }

    //加入注解的使用,不加入这个注解不生效
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

 注:session管理

对所有请求都校验请求头中是否有token,shiro会校验取到的token(sessionid)是否有效,验证会话状态

package com.zcb.minimalladminapi.config;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

/**
 * 自定义sessionId获取
 */
public class AdminSessionManager extends DefaultWebSessionManager {

    private static final String AUTHORIZATION = "X-Minimall-Admin-Token";

    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    public AdminSessionManager() {
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        //如果请求头中有 Authorization 则其值为sessionId
        if (!StringUtils.isEmpty(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            //System.out.println("Authorization="+id);
            return id;
        } else {
            //否则按默认规则从cookie取sessionId
            //System.out.println("sessionId="+super.getSessionId(request, response));
            return super.getSessionId(request, response);
        }
    }
}

 3.登录

subject.login(token)会调用自定义的realm实现认证

sessionId当作token给前端处理,前端请求时带着用来鉴权(会话状态)等。

/**
     * 登录
     * @param body {username,password}
     * @param request
     * @return {errno,errmsg}
     */
    @PostMapping(value = "/login")
    public JSONObject login(@RequestBody String body, HttpServletRequest request) {
        String username = ParseJsonUtil.parseString(body, "username");
        String password = ParseJsonUtil.parseString(body, "password");

        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            return ResponseUtil.badArgument(); //用户名或密码为空
        }
        Subject subject= SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try{
            //为当前用户进行认证,授权
            subject.login(token);
            //登录成功则返回sessionId作为token给前端存储,
            //前端请求时将该token放入请求头,以此来鉴权
            Session session=subject.getSession();
            Serializable sessionId = session.getId();

            return ResponseUtil.ok(sessionId);
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            return ResponseUtil.fail(1, "用户名不存在"); //系统错误
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            return ResponseUtil.fail(1, "密码错误"); //系统错误
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseUtil.fail(1, "登录失败");
        }

    }

 注:

@RequiresPermissions注解--需要subject具有相应的权限才能访问

@RequiresRoles--需要相应的角色才能访问

 例子 create()方法需要subject具有admin:admin:create权限

@RequiresPermissions("admin:admin:create")
    @PostMapping(value = "/create")
    public JSONObject create(@RequestBody Admin admin) {
       doSomething..
    }

项目地址:spring boot整合shiro项目代码

你可能感兴趣的:(spring,boot,shiro)