0. 旨要
用于学习和记录,以方便后期开发和查找。第一次写文章,如有问题,感谢指出!!!
1. Shiro 简单介绍
Shiro是一个功能强大且易于使用的Java安全框架,它执行身份验证,授权,加密和会话管理。
三个核心组件:
- Subject :用户主体(把操作交给SecurityManager)
- SecurityManager :安全管理器(关联Realm)
- Realm :shiro连接数据的桥梁
2. 项目实战
环境:springboot + maven + mybatis
工具:IDEA
注:springboot 项目搭建就不多说。
2.1 依赖包
maven配置
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- shiro 权限控制-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2.2 验证授权配置
@Slf4j
public class UserRealm extends AuthorizingRealm {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private SysMenuMapper sysMenuMapper;
/**
* 执行授权逻辑
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SysUserEntity principal = (SysUserEntity) principalCollection.getPrimaryPrincipal();
Set<String> roleCodeSet = new HashSet<>();//用户角色列表
Set<String> permsSet = new HashSet<>();//用户权限列表
SysUserRoleEntity sysUserRolesEntity = sysUserMapper.selectAllRoleMenusByUserId(principal.getId());
List<SysRoleEntity> roles = sysUserRolesEntity.getSysRoleList();
for (SysRoleEntity role : roles) {
roleCodeSet.add(role.getCode());
if (CollectionUtils.isNotEmpty(role.getSysMenuList())) {
role.getSysMenuList().stream().forEach(sysMenu -> permsList.add(sysMenu.getPerms()));
}
}
for (String perm : permsList) {
if (StringUtils.isNotBlank(perm)) {
permsSet.addAll(Arrays.asList(perm.trim().split(",")));
}
}
SimpleAuthorizationInfo authorInfo = new SimpleAuthorizationInfo();
authorInfo.setRoles(roleCodeSet);
authorInfo.setStringPermissions(permsSet);
return authorInfo;
}
/**
* 执行认证逻辑,登录时调用
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
SysUserEntity user = sysUserMapper.selectByUsername(token.getUsername());
if (user == null) {
throw new UnknownAccountException("账号不存在");
}
SimpleAuthenticationInfo authInfo = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
return authInfo;
}
}
这里主要要说明下认证时的SimpleAuthenticationInfo
对象,一共4个参数,按顺序说明:
- 第一个参数为当前用户信息,可以是用户的实体类,也可以是单单的一个用户账号,看个人需求,不过一般都是使用实体类;
- 第二个参数为数据库所存储的用户密码,subject.login时用于与token中的密码进行匹配,匹配不上会自动报异常(IncorrectCredentialsException);
- 第三个参数为盐,用于加密密码对比。常用的MD5、SHA256等等。注意,使用盐时数据库存储的密码也应是加密后的字符串(可以不设置);
- 第四个参数,当前 Realm 名称
2.3 配置
@Configuration
public class ShiroConfig {
/**
* 创建 Realm
*/
@Bean
public UserRealm myShiroRealm(CredentialsMatcher matcher) {
UserRealm realm = new UserRealm();
realm.setCredentialsMatcher(matcher);
return realm;
}
/**
* 创建安全管理器
*/
@Bean
public SecurityManager securityManager(UserRealm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
/**
* 配置过滤器
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
Map<String, String> filterChainMap = new LinkedHashMap<>();
filterChainMap.put("/webjars/**", "anon");
filterChainMap.put("/logout", "logout");
filterChainMap.put("/login", "anon");
filterChainMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
}
/**
* 密码加密验证
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("SHA-256");
matcher.setHashIterations(16);
return matcher;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 开启aop注解支持
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
2.3.1 ShiroFilterFactoryBean 说明
主要说明下loginUrl
和 filterChainMap
1) loginUrl
没有登录的用户请求需要登录的页面时自动跳转到登录页面,可配置也可不配置,但是不配置的话,会默认查找web下的login.jsp;
2) filterChainMap
Shiro内置过滤器,可以实现权限相关的拦截(url),依照顺序优先匹配,可以使用自定义的filter去覆盖。这里说的只是默认的拦截,没涉及到自定义filter。以下是一些常用的拦截配置说明:
- anon: 无需认证
- authc: 需要认证才可以访问
- user: 如果使用了rememberMe的功能可以直接访问
- perms: 该资源必须得到资源权限才可以访问
- roles: 该资源必须得到角色权限才可以访问
<font color=red>注:配置时注意别拦截自己的静态资源</font>
2.4 使用
以上1、2、3 步骤已经完成了Shiro的简单配置,也可以使用了
2.4.1 登录
@PostMapping("/login")
public String doLogin(String username, String password, Model model) {
//1. 封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//2. 获取Subject
Subject subject = SecurityUtils.getSubject();
//3. 执行登录操作
try {
subject.login(token);
} catch (IncorrectCredentialsException ice) {
model.addAttribute("msg", "密码不正确");
return "login";
} catch (UnknownAccountException uae) {
model.addAttribute("msg", "账号不存在");
return "login";
} catch (AuthenticationException ae) {
model.addAttribute("msg", "状态不正常");
return "login";
}
model.addAttribute("username", username);
return "index";
}
2.4.2 控制器访问权限控制
@RequestMapping("/info")
@RequiresPermissions("sys:user:info")//需要在Shiro配置文件里配置AOP才能生效
public R getUserInfo(Long userId){
...
return R;
}
2.4.3 Shiro获取用户主体
SecurityUtils.getSubject().getPrincipal();
网友评论