本文将不会介绍 Shiro 的原理,而是会直接说明 Spring Boot 如何与 Shiro 集成,若对 Shiro 还不了解的同学可以看我之前的文章:Shiro 关于认证与授权功能的实现
1 引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<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>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
2 创建 pojo
我们创建三个类,分别模拟数据库中的用户表,角色表与权限表
用户类
package edu.szu.test.entity;
import java.util.HashSet;
import java.util.Set;
public class User {
private Integer uid;
private String username;
private String password;
private Set<Role> roles = new HashSet<Role>();
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
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 Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
角色类
package edu.szu.test.entity;
import java.util.HashSet;
import java.util.Set;
public class Role {
private Integer rid;
private String rname;
private Set<Permission> permissions = new HashSet<Permission>();
public Integer getRid() {
return rid;
}
public void setRid(Integer rid) {
this.rid = rid;
}
public String getRname() {
return rname;
}
public void setRname(String rname) {
this.rname = rname;
}
public Set<Permission> getPermissions() {
return permissions;
}
public void setPermissions(Set<Permission> permissions) {
this.permissions = permissions;
}
}
权限类
package edu.szu.test.entity;
public class Permission {
private Integer pid;
private String name;
public Integer getPid() {
return pid;
}
public void setPid(Integer pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Permission(Integer pid, String name) {
super();
this.pid = pid;
this.name = name;
}
}
3 创建服务类
我们创建服务类来模拟数据库的查询,这里我直接把数据写死,返回了一条自定义的数据
package edu.szu.test.service;
import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
import org.springframework.stereotype.Service;
import edu.szu.test.entity.Permission;
import edu.szu.test.entity.Role;
import edu.szu.test.entity.User;
@Service
public class UserService {
//根据用户名查询返回用户
//我这里使用静态数据代替数据库查询
public User getUserByUsername(String username) {
User user = new User();
Set<Role> set = new HashSet<Role>();
Role role = new Role();
Set<Permission> set1 = new HashSet<Permission>();
set1.add(new Permission(1, "edit"));
role.setPermissions(set1);
role.setRid(1);
role.setRname("admin");
set.add(role);
user.setRoles(set);
user.setPassword(new SimpleHash("MD5","123456",ByteSource.Util.bytes("Ling"),1024).toHex());//密码明文为123456,盐值为用户名
user.setUid(1);
user.setUsername(username);
return user;
}
}
4 新建一个 Realm
Realm 用于实现认证与授权功能
package edu.szu.test.config;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
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.authc.UsernamePasswordToken;
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 org.springframework.beans.factory.annotation.Autowired;
import edu.szu.test.entity.Permission;
import edu.szu.test.entity.Role;
import edu.szu.test.entity.User;
import edu.szu.test.service.UserService;
public class MyRealm extends AuthorizingRealm{
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取User
User user = (User) principals.getPrimaryPrincipal();
List<String> permissionList = new ArrayList<String>();
List<String> roleNameList = new ArrayList<String>();
Set<Role> roleSet = user.getRoles();
if (roleSet != null){
for (Role role : roleSet){
roleNameList.add(role.getRname());
Set<Permission> permissionSet = role.getPermissions();
if (permissionSet != null){
for (Permission permission : permissionSet){
permissionList.add(permission.getName());
}
}
}
}
// 把角色和权限放入info中
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 权限设定
info.addStringPermissions(permissionList);
// 角色设定
info.addRoles(roleNameList);
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
AuthenticationInfo info = null;
//获取用户名
String username = upToken.getUsername();
//查询数据库.我这里直接写死
User user = userService.getUserByUsername(username);
if(user != null) {
//获取盐值,这里为我们的用户名
ByteSource salt = ByteSource.Util.bytes("Ling");
String realmName = getName();
// 将用户,密码,盐值,realmName实例化到SimpleAuthenticationInfo中交给Shiro来管理
info = new SimpleAuthenticationInfo(user,user.getPassword(),salt,realmName);
}else {
// 如果没有查询到,抛出一个异常
throw new AuthenticationException();
}
return info;
}
}
5 实现 Shiro 配置类
/**
* Shiro配置类
* @author 30309
*
*/
@Configuration
public class ShiroConfig {
//设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//未登陆时,统一跳转至login.jsp
shiroFilterFactoryBean.setLoginUrl("/login");
//成功登陆后,统一跳转至index.jsp
shiroFilterFactoryBean.setSuccessUrl("/index");
//访问没有权限访问的界面,统一跳转至out.jsp
shiroFilterFactoryBean.setUnauthorizedUrl("/out");
//设置权限
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// index界面需要鉴权
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/index", "authc");
// login、loginUser不需要验证
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/loginUser", "anon");
// admin需要角色admin才能访问
filterChainDefinitionMap.put("/admin", "roles[admin]");
// edit需要权限edit才能访问
filterChainDefinitionMap.put("/edit", "perms[edit]");
filterChainDefinitionMap.put("/**", "user");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//将自己的验证方式加入容器
@Bean(name = "myRealm")
public MyRealm myRealm(HashedCredentialsMatcher matcher) {
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(matcher);
return myRealm;
}
//Realm的管理认证
@Bean
public DefaultWebSecurityManager securityManager(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm(matcher));
return securityManager;
}
//密码匹配凭证管理器
@Bean(name = "hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//采用MD5方式加密
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//设置加密次数
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
}
6 创建控制器
package edu.szu.test.controller;
import javax.servlet.http.HttpSession;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import edu.szu.test.entity.User;
@Controller
public class TestController{
@RequestMapping("/login")
public String login(){
return "login";
}
@RequestMapping("/index")
public String index(){
return "index";
}
//退出登录
@RequestMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
if (subject != null){
subject.logout();
}
return "login";
}
@RequestMapping("/admin")
@ResponseBody
public String admin(){
return "admin页面可以成功被访问";
}
@RequestMapping("/edit")
@ResponseBody
public String edit(){
return "edit页面可以成功被访问";
}
@RequestMapping("/loginUser")
public String loginUser(@RequestParam("username") String username,@RequestParam("password") String password,HttpSession session){
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try{
subject.login(token);
User user = (User) subject.getPrincipal();
session.setAttribute("user", user);
return "index";
}
catch (Exception e){
return "login";
}
}
}
7 测试
我们之前设置的用户是拥有 admin 角色与 edit 权限的,即该用户是可以访问 /admin 和 /edit 的。
我们先访问 http://localhost:8080/admin,发现会跳转至登录页面
输入账号密码,会跳转至 index.jsp
然后我们再访问 http://localhost:8080/admin,发现可以访问该页面
证明我们已经登录成功。
8 使用注解控制鉴权授权
我们修改一下 Shiro 配置类,加上对注解的使用
//加入注解的使用
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
//加入注解的使用
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager(matcher));
return authorizationAttributeSourceAdvisor;
}
//加入注解的使用
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
再修改一下过滤条件
//设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//未登陆时,统一跳转至login.jsp
shiroFilterFactoryBean.setLoginUrl("/login");
//成功登陆后,统一跳转至index.jsp
shiroFilterFactoryBean.setSuccessUrl("/index");
//访问没有权限访问的界面,统一跳转至out.jsp
shiroFilterFactoryBean.setUnauthorizedUrl("/out");
//设置权限
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
/**
// index界面需要鉴权
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/index", "authc");
// login、loginUser不需要验证
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/loginUser", "anon");
// admin需要角色admin才能访问
filterChainDefinitionMap.put("/admin", "roles[admin]");
// edit需要权限edit才能访问
filterChainDefinitionMap.put("/edit", "perms[edit]");
**/
//所有路径均可匿名访问
filterChainDefinitionMap.put("/**", "anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
然后我们修改下这个用户的信息,取消 edit 权限,保持原来的 admin 角色不变
再修改一下控制器
@Controller
public class TestController{
//没有加上@RequiresAuthentication注解
//可以匿名访问
@RequestMapping("/login")
public String login(){
return "login";
}
//已登录用户才能访问
// 如果用户未登录调用该接口,会抛出UnauthenticatedException
@RequiresAuthentication
@RequestMapping("/index")
public String index(){
return "index";
}
//退出登录
@RequiresAuthentication
@RequestMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
if (subject != null){
subject.logout();
}
return "login";
}
//要求登录的用户具有admin角色才能访问
//我们设置的用户有这个角色,所以可以访问
//如果没有登录,会抛出UnauthenticatedException
@RequiresRoles("admin")
@RequestMapping("/admin")
@ResponseBody
public String admin(){
return "admin页面可以成功被访问";
}
//要求登录的用户具有edit权限才能访问
//我们设置的用户并没有这个权限,所以不能访问
//注意:如果没有登录,会抛出UnauthenticatedException
//如果登录了,但是没有这个权限,会报错UnauthorizedException
@RequiresPermissions("edit")
@RequestMapping("/edit")
@ResponseBody
public String edit(){
return "edit页面可以成功被访问";
}
@RequestMapping("/loginUser")
public String loginUser(@RequestParam("username") String username,@RequestParam("password") String password,HttpSession session){
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try{
subject.login(token);
User user = (User) subject.getPrincipal();
session.setAttribute("user", user);
return "index";
}
catch (Exception e){
return "login";
}
}
}
进入登录页面
然后访问 /admin
访问 /edit 失败(没有这个权限)
参考:
SpringBoot集成Shiro思路以及代码过程
Shiro用starter方式优雅整合到SpringBoot中