springboot结合shiro的demo

1,573 阅读9分钟

项目结构

在这里插入图片描述
在这里插入图片描述

如图所示,项目一共分为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