共计 5860 个字符,预计需要花费 15 分钟才能阅读完成。
前言
最近在新写项目的时候,有需要用到大量的权限控制的地方,如果自己手动写太耗时了,所以就采用了 Shiro 进行权限控制与登录验证,下面我们来看看如何整合他们。
正文
添加依赖
首先,我们先将 maven 依赖添加到项目的 pom.xml 中:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
添加配置文件
然后添加 Shiro 配置,在 Springboot 中都是采用注解方式进行注册:
@Configuration
public class ShiroConfiguration {private Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){logger.info("ShiroConfiguration initialized");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
// 登出
filterChainDefinitionMap.put("/logout", "logout");
//<!-- 过滤链定义,从上向下顺序执行,一般将 /** 放在最为下边 -->: 这是一个坑呢,一不小心代码就不好使了;
//<!-- authc: 所有 url 都必须认证通过才可以访问; anon: 所有 url 都都可以匿名访问 -->
filterChainDefinitionMap.put("/admin/**", "authc");
filterChainDefinitionMap.put("/**", "anon");
// 如果不设置默认会自动寻找 Web 工程根目录下的 "/login.jsp" 页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(MyRealm myRealm){DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm);
return manager;
}
// 开启 shiro aop 注解支持
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* DefaultAdvisorAutoProxyCreator,Spring 的一个 bean,由 Advisor 决定对哪些类的方法进行 AOP 代理。*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
}
身份效验
接着在 Shiro 配置下新建一个 MyRealm.java
,继承 AuthorizingRealm
:
public class MyRealm extends AuthorizingRealm {private final static Logger logger = LoggerFactory.getLogger(MyRealm.class);
@Resource
private UserService userService;
@Resource
private RoleService roleService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {logger.debug("################## 执行 Shiro 权限认证 ##################");
User user = (User) principalCollection.getPrimaryPrincipal();
if(user!=null){SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Role role = roleService.getById(user.getRole().getRId());
List<Permission> permissions = role.getPermissions();
for (Permission p : permissions){info.addStringPermission(p.getPAlias());
}
info.addRole(role.getRAlias());
return info;
}
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
logger.info("用户验证执行 :"+token.getUsername());
User user = userService.getByEmail(token.getUsername(),true);
if(user==null){logger.error("用户 {"+token.getUsername()+"} 不存在");
throw new AccountException("账户不存在");
}
if(user.getStatus()==0){logger.error("用户 {"+token.getUsername()+"} 被禁止登录");
throw new DisabledAccountException("账号已经禁止登录");
}else{user.setUpdated(DateUtils.getNowTimestamp());
user.setUpdatedAt(DateUtils.getNowFormatDate(null));
System.out.println("效验更新前 ROLE:"+user.getRole().getRId());
userService.update(user,true,user.getId());
}
return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
}
@PostConstruct
public void initCredentialsMatcher() {
// 该句作用是重写 shiro 的密码验证,让 shiro 用我自己的验证
setCredentialsMatcher(new CredentialsMatcher());
}
}
public class CredentialsMatcher extends SimpleCredentialsMatcher {private final static Logger LOGGER = LoggerFactory.getLogger(CredentialsMatcher.class);
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {UsernamePasswordToken authcToken = (UsernamePasswordToken) token;
Object tokenCredentials = EncryptUtils.md5(authcToken.getUsername()+String.valueOf(authcToken.getPassword()));
Object accountCredentials = getCredentials(info);
return accountCredentials.equals(tokenCredentials);
}
}
@Data
public class User implements Serializable {
/**
* 用户 ID
*/
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String phone;
/**
* 密码
*/
private String password;
/**
* 角色
*/
private Role role;
/**
* 状态:1- 正常,0- 封禁
*/
private Integer status;
/**
* 头像
*/
private String avatar;
/**
* 创建时间
*/
private Long created;
/**
* 修改时间
*/
private Long updated;
/**
* 创建时间字符串
*/
private String createdAt;
/**
* 修改时间字符串
*/
private String updatedAt;
private static final long serialVersionUID = 1L;
}
@Data
public class Role implements Serializable {
/**
* 角色唯一标识符
*/
private Integer rId;
/**
* 角色名称
*/
@NotNull(message = "角色名称不能为空")
private String rName;
/**
* 角色别名
*/
@NotNull(message = "角色别名不能为空")
private String rAlias;
/**
* 创建时间
*/
private Long created;
/**
* 修改时间
*/
private Long updated;
/**
* 创建时间字符串
*/
private String createdAt;
/**
* 修改时间字符串
*/
private String updatedAt;
private List<Permission> permissions;
private static final long serialVersionUID = 1L;
}
@Data
public class Permission implements Serializable {
/**
* 权限唯一标识 ID
*/
private Integer pId;
/**
* 权限名称
*/
private String pName;
/**
* 权限别名
*/
private String pAlias;
/**
* 创建时间
*/
private Long created;
/**
* 修改时间
*/
private Long updated;
/**
* 创建时间字符串
*/
private String createdAt;
/**
* 修改时间字符串
*/
private String updatedAt;
private static final long serialVersionUID = 1L;
}