0%

springboot结合shiro的demo

项目结构

在这里插入图片描述

如图所示,项目一共分为6层,分别是:

  • common公共层:主要是放置一些公共的模块
  • controller层:数据的表示层,俗称vo
  • dao层: 用于操作数据库,增删改查
  • Exception异常层:定义一些全局的异常,方便维护
  • model层: 数据库表的映射
  • shiro层:主要是配置shiro的授权和认证

    认证过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // 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用于授权。

授权过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 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了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
* @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种。

在这里插入图片描述

那么如何生成呢?

1
2
String wuyilong = new SimpleHash(Md5Hash.ALGORITHM_NAME, "123456", ByteSource.Util.bytes("wuyilong"), 1).toBase64();
System.out.println(wuyilong);

shiro里面自带有生成的类,我们直接调用即可。

最后

其他的就不多说了,需要的盆友,请到我的github上下载。
项目地址:springboot-shiro

-------------本文结束感谢你的阅读---------