springboot结合shiro的demo
原创大约 4 分钟
项目结构
如图所示,项目一共分为6层,分别是:
- common公共层:主要是放置一些公共的模块
- controller层:数据的表示层,俗称vo
- dao层: 用于操作数据库,增删改查
- Exception异常层:定义一些全局的异常,方便维护
- model层: 数据库表的映射
- shiro层:主要是配置shiro的授权和认证
认证过程
// LoginController.java
@PostMapping("/login")
public Response login(@RequestBody User user) {
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getUsername(),user.getPassword());
Subject subject = SecurityUtils.getSubject();
subject.login(usernamePasswordToken);
if(!subject.isAuthenticated()) {
throw new AuthenticationException("认证失败");
}
return new Response(200,"登录成功",user);
}
// CustomRealm.java
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("**************authenticationToken->{}", JSONUtil.toJsonStr(authenticationToken));
if(authenticationToken.getPrincipal() == null) {
return null;
}
// 获得用户名
String username = authenticationToken.getPrincipal().toString();
User user = userDao.findByUsername(username);
if(user == null) {
throw new UnknownAccountException("用户名或密码错误");
}
// 认证信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes("wuyilong"),getName());
log.info("**************返回认证结果->{}", JSONUtil.toJsonStr(simpleAuthenticationInfo));
return simpleAuthenticationInfo;
}
用户发起登录请求,经过login控制器,接受用户名和密码初始化一个用户密码认证token,然后再用SecurityUtils(这个封装了SecurityManager)获得当前环境的主体Subject、,通过login的方法,接收token,实质上就是SecurityManager里面的login方法,这里进行跳转到我们自定义的CustomRealm,进行真正的认证,认证通过就会返回simpleAuthenticationInfo,实质上就是principa(代码上的user.getUsername())l用于授权。
授权过程
// UserController.java
@RequiresPermissions("/user/page")
@GetMapping("/page")
public Response<User> getUserPage(@RequestParam(value = "pageNum",defaultValue = "1") Integer pageNum,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
Sort sort = Sort.by(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(pageNum-1, pageSize, sort);
Page<User> userPage = userDao.findAll(pageable);
return new Response(200,"操作成功",userPage);
}
// CustomRealm.java
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("**********principalCollection->{}",principalCollection);
// 获得用户名 这个用户名是从认证来的
String username = principalCollection.getPrimaryPrincipal().toString();
User user = userDao.findByUsername(username);
// 授权信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 添加角色
Role role = roleDao.findById(user.getRoleId()).get();
simpleAuthorizationInfo.addRole(role.getRoleName());
// 添加权限
List<RolePermission> rolePermissionListList = rolePermissionDao.findByRoleId(role.getId());
rolePermissionListList.stream()
.map(rolePermission -> permissionDao.findById(rolePermission.getPermissionId()).get())
.map(Permission::getAction)
.forEach(simpleAuthorizationInfo::addStringPermission);
log.info("*********返回授权结果->{}",JSONUtil.toJsonStr(simpleAuthorizationInfo));
return simpleAuthorizationInfo;
}
用户首先访问带有权限控制的注解@RequiresPermissions,从而进入授权过程。查询数据库当前用户的角色和权限,分别添加到simpleAuthorizationInfo并返回.这个时候我们还不清楚到底是怎么验证授权的,只有debug了!
public void assertAuthorized(Annotation a) throws AuthorizationException {
if (!(a instanceof RequiresPermissions)) return;
RequiresPermissions rpAnnotation = (RequiresPermissions) a;
String[] perms = getAnnotationValue(a);
Subject subject = getSubject();
if (perms.length == 1) {
subject.checkPermission(perms[0]);
return;
}
if (Logical.AND.equals(rpAnnotation.logical())) {
getSubject().checkPermissions(perms);
return;
}
if (Logical.OR.equals(rpAnnotation.logical())) {
// Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
boolean hasAtLeastOnePermission = false;
for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;
// Cause the exception if none of the role match, note that the exception message will be a bit misleading
if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);
}
}
}
debug到里面你会看到如上的代码,可以看到,解析了注解上的权限标识,并检测当前环境下的主体用户是否拥有这个权限。
ShiroConfig
/**
* @ClassName ShiroConfig shiro的配置文件
* @Description
* @Author yilongwu
* @DATE 2020-03-11 15:34
* @Version 1.0.0
**/
@Configuration
@Slf4j
public class ShiroConfig {
/**
* 把自定义的CustomRealm注入到spring容器中
* @return
*/
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm = new CustomRealm();
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return customRealm;
}
/**
* 注入securityManager
* @return
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm());
return securityManager;
}
// 设置用于匹配密码的CredentialsMatcher
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName(Md5Hash.ALGORITHM_NAME); // 散列算法,这里使用更安全的sha256算法
credentialsMatcher.setStoredCredentialsHexEncoded(false); // 数据库存储的密码字段使用HEX还是BASE64方式加密
credentialsMatcher.setHashIterations(1); // 散列迭代次数
return credentialsMatcher;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 这个在做前后端分离时,如果你没有登录就访问其他的资源就会跳到这个设置的url
shiroFilterFactoryBean.setLoginUrl("/notLogin");
// 没有权限时进行跳转(ps:在没有使用注解的情况下能自动捕获异常,并跳转到该指定的路径)
//shiroFilterFactoryBean.setUnauthorizedUrl("/unAuthorized");
// 设置拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 开放接口
filterChainDefinitionMap.put("/login","anon");;
// 其余的需要认证
filterChainDefinitionMap.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//加入注解的使用,不加入这个注解不生效
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
这个配置文件有很多注释了,并不用多说,这里要说的是密码匹配器,其实还有很多密码匹配器的 查看源代码就知道,一共有6种。
那么如何生成呢?
String wuyilong = new SimpleHash(Md5Hash.ALGORITHM_NAME, "123456", ByteSource.Util.bytes("wuyilong"), 1).toBase64();
System.out.println(wuyilong);
shiro里面自带有生成的类,我们直接调用即可。
最后
其他的就不多说了,需要的盆友,请到我的github上下载。 项目地址:springboot-shiro