项目结构

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

那么如何生成呢?
1 String wuyilong = new SimpleHash(Md5Hash.ALGORITHM_NAME, "123456", ByteSource.Util.bytes("wuyilong"), 1).toBase64();
2 System.out.println(wuyilong);
shiro里面自带有生成的类,我们直接调用即可。
最后
其他的就不多说了,需要的盆友,请到我的github上下载。
项目地址:springboot-shiro