之前介绍了springboot使用security进行权限管理,这篇文件介绍一下springboot使用shiro进行安全管理。
简述本文的场景,本文使用springboot1.5.9+mysql+jpa+thymeleaf+shiro制作一个简单的验证,其中有2个角色,分别是admin和user,admin可以使用select和delete功能,user只能使用select功能。
新建项目,加入shiro依赖,pom文件如下:
4.0.0
com.dalaoyang
springboot_shiro
0.0.1-SNAPSHOT
jar
springboot_shiro
springboot_shiro
org.springframework.boot
spring-boot-starter-parent
1.5.9.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-thymeleaf
net.sourceforge.nekohtml
nekohtml
1.9.15
org.apache.shiro
shiro-spring
1.4.0
org.springframework.boot
spring-boot-maven-plugin
配置文件如下:
##端口号
server.port=8888
##数据库配置
##数据库地址
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=utf8&useSSL=false
##数据库用户名
spring.datasource.username=root
##数据库密码
spring.datasource.password=root
##数据库驱动
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
##validate 加载hibernate时,验证创建数据库表结构
##create 每次加载hibernate,重新创建数据库表结构,这就是导致数据库表数据丢失的原因。
##create-drop 加载hibernate时创建,退出是删除表结构
##update 加载hibernate自动更新数据库结构
##validate 启动时验证表的结构,不会创建表
##none 启动时不做任何操作
spring.jpa.hibernate.ddl-auto=update
##控制台打印sql
spring.jpa.show-sql=true
# 建议在开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
##去除thymeleaf的html严格校验
spring.thymeleaf.mode=LEGACYHTML5
创建了三个实体类,分别是
SysUser(用户表)
package com.dalaoyang.entity;
import org.hibernate.validator.constraints.NotEmpty;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
/**
* @author dalaoyang
* @Description
* @project springboot_learn
* @package com.dalaoyang.entity
* @email [email protected]
* @date 2018/5/2
*/
@Entity
public class SysUser implements Serializable {
@Id
@GeneratedValue
private Integer userId;
@NotEmpty
private String userName;
@NotEmpty
private String passWord;
//多对多关系
@ManyToMany(fetch= FetchType.EAGER)
//急加载,加载一个实体时,定义急加载的属性会立即从数据库中加载
//FetchType.LAZY:懒加载,加载一个实体时,定义懒加载的属性不会马上从数据库中加载
@JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "userId") },
inverseJoinColumns ={@JoinColumn(name = "roleId") })
private List roleList;// 一个用户具有多个角色
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
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 List getRoleList() {
return roleList;
}
public void setRoleList(List roleList) {
this.roleList = roleList;
}
}
SysRole(角色表)
package com.dalaoyang.entity;
import org.hibernate.validator.constraints.NotEmpty;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
/**
* @author dalaoyang
* @Description
* @project springboot_learn
* @package com.dalaoyang.entity
* @email [email protected]
* @date 2018/5/2
*/
@Entity
public class SysRole implements Serializable {
@Id
@GeneratedValue
private Integer roleId;
private String roleName;
//多对多关系
@ManyToMany(fetch= FetchType.EAGER)
@JoinTable(name="SysRoleMenu",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="menuId")})
private List menuList;
//多对多关系
@ManyToMany
@JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="userId")})
private List userList;// 一个角色对应多个用户
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public List getMenuList() {
return menuList;
}
public void setMenuList(List menuList) {
this.menuList = menuList;
}
public List getUserList() {
return userList;
}
public void setUserList(List userList) {
this.userList = userList;
}
}
SysMenu(菜单表)
package com.dalaoyang.entity;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
/**
* @author dalaoyang
* @Description
* @project springboot_learn
* @package com.dalaoyang.entity
* @email [email protected]
* @date 2018/5/2
*/
@Entity
public class SysMenu implements Serializable {
@Id
@GeneratedValue
private Integer menuId;
private String menuName;
@ManyToMany
@JoinTable(name="SysRoleMenu",joinColumns={@JoinColumn(name="menuId")},inverseJoinColumns={@JoinColumn(name="roleId")})
private List roleList;
public Integer getMenuId() {
return menuId;
}
public void setMenuId(Integer menuId) {
this.menuId = menuId;
}
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public List getRoleList() {
return roleList;
}
public void setRoleList(List roleList) {
this.roleList = roleList;
}
}
创建一个UserRepository用于查询用户信息:
package com.dalaoyang.repository;
import com.dalaoyang.entity.SysUser;
import org.springframework.data.repository.CrudRepository;
/**
* @author dalaoyang
* @Description
* @project springboot_learn
* @package com.dalaoyang.repository
* @email [email protected]
* @date 2018/5/2
*/
public interface UserRepository extends CrudRepository {
SysUser findByUserName(String username);
}
创建几个前台页面进行测试,分别是:
login.html:
Login
错误信息:
index.html
Title
index
delete.html
Title
delete
select.html
Title
select
403.html
Title
403
创建一个ShiroConfig,代码如下:
package com.dalaoyang.config;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
/**
* @author dalaoyang
* @Description
* @project springboot_learn
* @package com.dalaoyang.config
* @email [email protected]
* @date 2018/5/2
*/
@Configuration
public class ShiroConfig {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
logger.info("启动shiroFilter--时间是:" + new Date());
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//shiro拦截器
Map filterChainDefinitionMap = new LinkedHashMap();
//
//
// 配置不被拦截的资源及链接
filterChainDefinitionMap.put("/static/**", "anon");
// 退出过滤器
filterChainDefinitionMap.put("/logout", "logout");
//配置需要认证权限的
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login"页面,即本文使用的login.html
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//自定义身份认证Realm(包含用户名密码校验,权限校验等)
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
//开启shiro aop注解支持,不开启的话权限验证就会失效
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
//配置异常处理,不配置的话没有权限后台报错,前台不会跳转到403页面
@Bean(name="simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver
createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
mappings.setProperty("UnauthorizedException","403");
simpleMappingExceptionResolver.setExceptionMappings(mappings); // None by default
simpleMappingExceptionResolver.setDefaultErrorView("error"); // No default
simpleMappingExceptionResolver.setExceptionAttribute("ex"); // Default is "exception"
return simpleMappingExceptionResolver;
}
}
在配置一个MyShiroRealm用于登录认证和授权认证,代码如下:
package com.dalaoyang.config;
import com.dalaoyang.entity.SysMenu;
import com.dalaoyang.entity.SysRole;
import com.dalaoyang.entity.SysUser;
import com.dalaoyang.repository.UserRepository;
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 javax.annotation.Resource;
/**
* @author dalaoyang
* @Description
* @project springboot_learn
* @package com.dalaoyang.config
* @email [email protected]
* @date 2018/5/2
*/
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private UserRepository userRepository;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
SysUser userInfo = (SysUser)principals.getPrimaryPrincipal();
for(SysRole role:userInfo.getRoleList()){
authorizationInfo.addRole(role.getRoleName());
for(SysMenu menu:role.getMenuList()){
authorizationInfo.addStringPermission(menu.getMenuName());
}
}
return authorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
//获得当前用户的用户名
String username = (String)token.getPrincipal();
System.out.println(token.getCredentials());
//根据用户名找到对象
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
SysUser userInfo = userRepository.findByUserName(username);
if(userInfo == null){
return null;
}
//这里会去校验密码是否正确
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo, //用户名
userInfo.getPassWord(),//密码
getName()
);
return authenticationInfo;
}
}
最后新建一个controller,其中本文使用了2种验证权限的方法,select方法使用@RequiresPermissions("select")来验证用户是否具有select权限,delete方法使用@RequiresRoles("admin")来验证用户是否是admin,代码如下:
package com.dalaoyang.controller;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* @author dalaoyang
* @Description
* @project springboot_learn
* @package com.dalaoyang.controller
* @email [email protected]
* @date 2018/5/2
*/
@Controller
public class TestController {
@GetMapping({"/","/index"})
public String index(){
return"index";
}
@GetMapping("/403")
public String unauthorizedRole(){
return "403";
}
@GetMapping("/delete")
//@RequiresPermissions("delete")
@RequiresRoles("admin")
public String delete(){
return "delete";
}
@GetMapping("/select")
@RequiresPermissions("select")
public String select(){
return "select";
}
@RequestMapping("/login")
public String login(HttpServletRequest request, Map map) throws Exception{
System.out.println("HomeController.login()");
// 登录失败从request中获取shiro处理的异常信息。
// shiroLoginFailure:就是shiro异常类的全类名.
String exception = (String) request.getAttribute("shiroLoginFailure");
String msg = "";
//根据异常判断错误类型
if (exception != null) {
if (UnknownAccountException.class.getName().equals(exception)) {
msg = "账号不存在";
} else if (IncorrectCredentialsException.class.getName().equals(exception)) {
msg = "密码不正确";
} else {
msg = "else >> "+exception;
}
}
map.put("msg", msg);
// 此方法不处理登录成功,由shiro进行处理
return "/login";
}
@GetMapping("/logout")
public String logout(){
return "/login";
}
}
为了方便测试,本人插入了几条初始数据,sql如下:
INSERT INTO `shiro`.`sys_menu`(`menu_id`, `menu_name`) VALUES (1, 'add');
INSERT INTO `shiro`.`sys_menu`(`menu_id`, `menu_name`) VALUES (2, 'delete');
INSERT INTO `shiro`.`sys_menu`(`menu_id`, `menu_name`) VALUES (3, 'update');
INSERT INTO `shiro`.`sys_menu`(`menu_id`, `menu_name`) VALUES (4, 'select');
INSERT INTO `shiro`.`sys_role`(`role_id`, `role_name`) VALUES (1, 'admin');
INSERT INTO `shiro`.`sys_role`(`role_id`, `role_name`) VALUES (2, 'user');
INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (1, 1);
INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (1, 2);
INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (1, 3);
INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (1, 4);
INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (2, 4);
INSERT INTO `shiro`.`sys_user`(`user_id`, `pass_word`, `user_name`) VALUES (1, '123', 'dalaoyang');
INSERT INTO `shiro`.`sys_user`(`user_id`, `pass_word`, `user_name`) VALUES (2, '123', 'xiaoli');
INSERT INTO `shiro`.`sys_user_role`(`role_id`, `user_id`) VALUES (1, 1);
INSERT INTO `shiro`.`sys_user_role`(`role_id`, `user_id`) VALUES (2, 2);
启动项目,我在这里就不一一截图了,口述一下,访问http://localhost:8888/select由于没有登录的原因,会自动跳转到http://localhost:8888/login,输入错误的用户名和密码会出现对应的提示。输入角色user的用户名xiaoli,密码123。访问http://localhost:8888/select页面会正常跳转,访问http://localhost:8888/delete会拦截到403页面。
如果使用角色为admin的用户dalaoyang密码123登录,以上请求全可以正常访问。
源码下载 :大老杨码云