SpringBoot2集成Shiro与Web应用

本文结合一个简单的权限模块设计来实现Shiro的集成。
新建实体如下:
权限实体Permission:id,code,name,parent_id;
角色实体Role:id,code,name;
用户实体User:id,username,password,role(简化设计,一个用户只能有一个角色,因此User表中设置一个role_id字段关联角色);
Role和Permission的关系通过role_permission关系表维护。

具体见源代码https://github.com/wu-boy/parker.git,parker-shiro-base模块,resources目录下有建表和初始化SQL。

SpringBoot集成Shiro引入shiro-spring-boot-web-starter即可。

首先自定义MyRealm,在这个Realm中做登录认证和用户授权。

package com.wu.parker.shiro.base.shiro;

import com.wu.parker.shiro.base.po.Permission;
import com.wu.parker.shiro.base.po.User;
import com.wu.parker.shiro.base.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

/**
 * @author: wusq
 * @date: 2018/12/8
 */
public class MyRealm extends AuthorizingRealm {

    private static final Logger log = LoggerFactory.getLogger(AuthorizingRealm.class);

    @Autowired
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("授权");
        String username = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = null;
        try {
            authorizationInfo = new SimpleAuthorizationInfo();
            User user = userService.findByUsername(username);
            authorizationInfo.addRole(user.getRole().getCode());
            List<Permission> list = user.getRole().getPermissionList();
            for(Permission p:list){
                authorizationInfo.addStringPermission(p.getCode());
            }
        } catch (Exception e) {
            log.error("授权错误{}", e.getMessage());
            e.printStackTrace();
        }
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        log.info("登录认证");

        String username = (String) token.getPrincipal();
        User user = userService.findByUsername(username);
        if(user == null) {
            throw new UnknownAccountException(); // 没找到帐号
        }

        /*if(Boolean.TRUE.equals(user.getLocked())) {
            throw new LockedAccountException(); //帐号锁定
        }*/

        //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以在此判断或自定义实现
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user.getUsername(), //用户名
                user.getPassword(), //密码
                getName()  //realm name
        );

        return authenticationInfo;
    }

}

ShiroConfig配置如下

package com.wu.parker.shiro.base.config;

import com.wu.parker.shiro.base.shiro.MyRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author: wusq
 * @date: 2018/12/8
 */
@Configuration
public class ShiroConfig {

    /**
     * 注入自定义的realm,告诉shiro如何获取用户信息来做登录认证和授权
     */
    @Bean
    public Realm realm() {
        return new MyRealm();
    }

    /**
     * 这里统一做鉴权,即判断哪些请求路径需要用户登录,哪些请求路径不需要用户登录。
     * 这里只做鉴权,不做权限控制,因为权限用注解来做。
     * @return
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();

        // 设置哪些请求可以匿名访问
        chain.addPathDefinition("/login/**", "anon");

        // 由于使用Swagger调试,因此设置所有Swagger相关的请求可以匿名访问
        chain.addPathDefinition("/swagger-ui.html", "anon");
        chain.addPathDefinition("/swagger-resources", "anon");
        chain.addPathDefinition("/swagger-resources/configuration/security", "anon");
        chain.addPathDefinition("/swagger-resources/configuration/ui", "anon");
        chain.addPathDefinition("/v2/api-docs", "anon");
        chain.addPathDefinition("/webjars/springfox-swagger-ui/**", "anon");

        //除了以上的请求外,其它请求都需要登录
        chain.addPathDefinition("/**", "authc");
        return chain;
    }

    @Bean
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        /**
         * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
         * 在@Controller注解的类的方法中加入@RequiresRole注解,会导致该方法无法映射请求,导致返回404。
         * 加入这项配置能解决这个bug
         */
        creator.setUsePrefix(true);
        return creator;
    }
}

新建PermissionController用来测试

package com.wu.parker.shiro.base.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: wusq
 * @date: 2018/12/8
 */
@Api(description = "资源服务")
@RestController
@RequestMapping("/security/permissions/")
public class PermissionController {

    @ApiOperation("查询资源")
    @GetMapping()
    @RequiresPermissions("permission:retrieve")
    public String get(){
        return "有permission:retrieve这个权限的用户才能访问,不然访问不了";
    }
}

新建LoginController完成登录功能

package com.wu.parker.shiro.base.controller;

import com.wu.parker.shiro.base.po.User;
import com.wu.parker.shiro.base.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: wusq
 * @date: 2018/12/8
 */
@Api(description = "登录服务")
@RestController
@RequestMapping("/login/")
public class LoginController {

    private static final Logger log = LoggerFactory.getLogger(LoginController.class);

    @Autowired
    private UserService userService;

    @ApiOperation("登录")
    @GetMapping("{username}/{password}")
    public User login(@PathVariable String username, @PathVariable String password){
        User result = null;
        Subject subject = SecurityUtils.getSubject();

        // 此处的密码应该是按照后台的加密规则加密过的,不应该传输明文密码
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try {
            subject.login(token);
            result = userService.findByUsername(username);
        } catch (UnknownAccountException e) {
            log.error("用户名或密码错误");
            e.printStackTrace();
        } catch (IncorrectCredentialsException e) {
            log.error("用户名或密码错误");
            e.printStackTrace();
        } catch (AuthenticationException e) {
            //其他错误,比如锁定,如果想单独处理请单独catch处理
            log.error("其他错误");
            e.printStackTrace();
        }
        return result;
    }
}

启动工程后,可以先访问PermissionController中的路径,会提示404,因为没有登录,被Shiro拦截了。
再测试登录功能,通过正确的用户名和密码登录后,再访问PermissionController会返回正常结果。

相关的注意事项都在代码注释中说明了。

附上加密工具类EncryptUtils,方便对Shiro的加密方式进行理解和测试
package com.wu.parker.common.encrypt;

import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 加解密工具类
 * @author: wusq
 * @date: 2018/12/8
 */
public class EncryptUtils {

    /**
     * 默认加密次数
     */
    public static final Integer DEFAULT_ITERATIONS = 1;

    /**
     * Shiro的MD5加密,加密方式是对字符串salt+password进行加密
     * @param salt 盐
     * @param password 密码
     * @return
     */
    public static String shiroMd5(String salt, String password){
        String algorithmName = "MD5";
        ByteSource byteSalt = ByteSource.Util.bytes(salt);
        SimpleHash simpleHash = new SimpleHash(algorithmName, password, byteSalt, DEFAULT_ITERATIONS);
        return simpleHash.toHex();
    }

    /**
     * Java的MD5加密,加密方式是对字符串salt+password进行加密
     * @param salt 盐
     * @param password
     * @return
     */
    public static String md5(String salt, String password){
        String result = null;
        byte[] bytes = null;
        try {
            // 生成一个MD5加密计算摘要
            MessageDigest md = MessageDigest.getInstance("MD5");
            // 对字符串进行加密
            md.update((salt + password).getBytes());
            // 获得加密后的数据
            bytes = md.digest();

            // 将加密后的数据转换为16进制数字
            result = new BigInteger(1, bytes).toString(16);// 16进制数字
            // 如果生成数字未满32位,需要前面补0
            for (int i = 0; i < 32 - result.length(); i++) {
                result = "0" + result;
            }
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("没有md5这个算法!");
        }
        return result;
    }

    public static void main(String[] args) {

        String password1 = shiroMd5("admin", "12345678");
        System.out.println(password1);

        String password2 = md5("admin", "12345678");
        System.out.println(password2);

        // 两者加密结果相同
        System.out.println(password1.equals(password2));
    }
}

源代码

https://github.com/wu-boy/parker.git
parker-shiro-base模块
EncryptUtils位于parker-common模块

参考资料

1、跟我学Shiro
2、Shiro用starter方式优雅整合到SpringBoot中
3、springboot(十四):springboot整合shiro-登录认证和权限管理
4、Shiro登陆异常 did not match the expected credentials. 是为什么

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值