SpringBoot框架整合Shiro

Apache Shiro 是一个功能强大且易于使用的 Java 安全框架,它为开发人员提供了一个直观而全面的身份验证、授权、加密和会话管理解决方案。

shiro框架的基本使用和介绍详情可见下面这篇文章供大家了解和参考:

https://www.jianshu.com/p/9c2da6734acd?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

1.创建一个SpringBoot项目并且添加如下依赖

本文使用的SpringBoot版本如下是2.7.0

引入依赖:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.20</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!-- shiro 注解会用到 aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.1.0</version>
        </dependency>


        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>

    </dependencies>

2.编写自定义的Realm继承AuthorizingRealm重写两个方法

1.doGetAuthorizationInfo方法:doGetAuthorizationInfo是授权的方法,在拦截器中进行权限校验的时候会调用。

2.doGetAuthenticationInfo方法:doGetAuthenticationInfo是认证的方法,用户首次登录的时候会进入这里进行密码校验。

public class TryShiroRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private PermissionService permissionService;

    /**
     * doGetAuthorizationInfo是授权的方法,在拦截器中进行权限校验的时候会调用
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //AuthorizationInfo 接口的简单 POJO 实现,将角色和权限存储为内部属性,获取角色的信息
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //这个可以用来获取在登录的时候提交的其他额外的参数信息
        // HttpServletRequest request = (HttpServletRequest) ((WebSubject)(SecurityUtils
        // .getSubject())).getServletRequest();
        String username = (String) principals.getPrimaryPrincipal();
        // 受理权限
        // 角色
        Set<String> roles = new HashSet<>();
        //查找并添加角色
        Role role = roleService.getRoleByUserName(username);
        System.out.println(role.getRoleName());
        roles.add(role.getRoleName());
        //设置角色名
        authorizationInfo.setRoles(roles);
        // 权限
        Set<String> permissions = new HashSet<>();
        List<Permission> queryPermissions = permissionService.getPermissionsByRoleId(role.getId());
        for (Permission permission : queryPermissions) {
            permissions.add(permission.getPermissionName());
        }
        //设置权限
        authorizationInfo.setStringPermissions(permissions);
        return authorizationInfo;
    }



    /**
     * doGetAuthenticationInfo是认证的方法
     * 用户首次登录的时候会进入这里
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        String loginName = (String) authenticationToken.getPrincipal();
        //获取用户的密码
        User user = userService.getOne(new QueryWrapper<User>().eq("username", loginName));
        if (user == null) {
            //不存在账号
            throw new UnknownAccountException();
        }
        String password = new String((char[]) authenticationToken.getCredentials());
        String inpass = (new Md5Hash(password, user.getUsername())).toString();
        //加密后的密码与数据库中的密码进行比较
        if (!user.getPassword().equals(inpass)) {
            throw new IncorrectCredentialsException();
        }
        // 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(loginName, user.getPassword(),
                ByteSource.Util.bytes(loginName), getName());
        return authenticationInfo;
    }
}

3.编写ShiroConfiguration

编写shiro的配置并注入。

ShiroFilterFactoryBean:定义主 Shiro 过滤器。

HashedCredentialsMatcher:HashedCredentialMatcher 在与数据存储中的 AuthenticationInfo 中的凭据进行比较之前,支持对提供的 AuthenticationToken 凭据进行散列处理。可以理解为加密方式。

SecurityManager:为单个应用程序中的所有主题(也称为用户)执行所有安全操作。也可以理解为扩展了 Authenticator、Authenticator 和 SessionManager 接口,从而将这些行为整合到一个单一的参考点中。对于大多数 Shiro 用法,这简化了配置,并且往往比分别引用 Authenticator、Authorizer 和 SessionManager 实例更方便;相反,只需要与单个 SecurityManager 实例进行交互。

@Configuration
public class ShiroConfiguration {

    /**
     * Sl4j日志输出
     */
    private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);

    /**
     * shiro的web过滤器Factory 命名为:shiroFilter
     * @param securityManager
     * @return
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // Shiro的核心安全接口,这个属性是必须的
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 需要权限的请求,如果没有登录则会跳转到这里设置的url
        shiroFilterFactoryBean.setLoginUrl("/login.html");
        // 设置登录成功跳转url,一般在登录成功后自己代码设置跳转url,此处基本没用
        shiroFilterFactoryBean.setSuccessUrl("/main.html");
        // 设置无权限跳转界面,此处一般不生效,一般自定义异常
        shiroFilterFactoryBean.setUnauthorizedUrl("/error.html");
        LinkedHashMap<String, Filter> filterMap = new LinkedHashMap<>();

        shiroFilterFactoryBean.setFilters(filterMap);
        /*
         * 定义shiro过滤器链 Map结构
         * Map中key(xml中是指value的值)的第一个‘/’代表的路径是相对于HttpServletRequest.getContextPath()的值来的
         * anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种
         * authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.
         * FormAuthenticationFilter
         */
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        /*
         *  过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
         *  authc:所有url都必须认证通过才可以访问;
         *  anon: 所有url都都可以匿名访问
         */
        filterChainDefinitionMap.put("/login.html", "authc");
        filterChainDefinitionMap.put("/user/login", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean(name = "credentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 散列算法:这里使用MD5算法
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 散列的次数
        hashedCredentialsMatcher.setHashIterations(1);
        // storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }

    /**
     * Shiro Realm 继承自AuthorizingRealm的自定义Realm
     * 即指定Shiro验证用户登录的类为自定义的
     * @return
     */
    @Bean
    public TryShiroRealm TryShiroRealm() {
        TryShiroRealm tryShiroRealm = new TryShiroRealm();
        tryShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return tryShiroRealm;
    }

    /**
     * 权限管理
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        logger.info("===========================shiro=========================");
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(TryShiroRealm());
        //
        return securityManager;
    }

    /**
     * 开启shiro注解(如@RequiresRoles,@RequiresPermission)
     * ,需借助SpringAOP扫描使用Shiro注解的类,并再必要时进行安全逻辑验证
     * 通过注入 Advisor 来增强一些类的和方法
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

}

4.自定义异常类

/**
 * 自定义异常类
 * @ControllerAdvice 本质上是一个Component,因此也会被当成组建扫描
 * 是aop思想的一种实现,你告诉我需要拦截规则,我帮你把他们拦下来
 * 具体你想做更细致的拦截筛选和拦截之后的处理,
 * 你自己通过@ExceptionHandler、@InitBinder 或 @ModelAttribute这三个注解以及被其注解的方法来自定义
 */
@ControllerAdvice
public class ShiroException {
	/**
	 * 接收Throwable类作为参数,我们知道Throwable是所有异常的父类,所以说,可以自行指定所有异常
	 * 拦截异常后进行处理
	 * @return
	 */
	@ExceptionHandler(value = UnauthorizedException.class)
	@ResponseBody
	public String name() {
		return "没有权限!";
	}
}

5.控制层代码:

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @PostMapping("/login")
    public String name(String username, String password) {
        String result = "已经登录";
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        if (!currentUser.isAuthenticated()) {
            try {
                // 会触发doGetAuthenticationInfo方法
                currentUser.login(token);
                result = "登录成功";
            } catch (UnknownAccountException e) {
                result = "用户名错误";
            } catch (IncorrectCredentialsException e) {
                result = "密码错误";
            }
        }
        return result;
    }

    @GetMapping("/logout")
    public void logout() {
        Subject currentUser = SecurityUtils.getSubject();
        currentUser.logout();
    }

    @RequiresPermissions("role:update")
    @GetMapping("/role")
    public String name() {
        return "hello";
    }

    @RequiresPermissions("user:select")
    @GetMapping("/role2")
    public String permission() {
        return "hello role2";
    }
}

6.源代码以及数据库文件拉取位置:

https://gitee.com/shao-xiaowang/spring-boot-shiro

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值