spring boot使用shiro做登录认证,权限验证,会话管理操作
1.需要三张表-用户表(user),角色表(role),权限表(permission)
建表语句
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
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
配置
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<Admin> 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<String> roles = rolesService.queryByIds(roleIds);
Set<String> 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<Admin> 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注解的使用
- 配置加密方式md5
- 加入自定义的认证方式
- 配置会话管理(用来认证token--token是sessionid)
- 配置安全管理员
- 配置过滤方式
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<String,String> map = new HashMap<String, String>();
//登出
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..
}