基于springboot接入shiro简单入门

2020-10-13  本文已影响0人  Martain

前言

前几天项目中要接入shiro,查看了它的官方教程以及示例代码,可以说已经是非常详细了。但是对于我这种之前没有接触过,马上要上手的情况来说,欠缺了如何在springboot项目中如何接入的教程,网上也有很多很好的博客写了springboot如何接入shiro,毕竟shiro并不是什么新东西,但是我看了许多博客都是配置的比较全的功能的,shiro是一个轻量级的框架,可以自定义添加需要哪些功能。一下子上来太多配置让我有点懵,所以我踩了一下坑之后,记录下了一个比较简单、轻量的接入文章,该项目只是简单的实现了认证功能,鉴权功能并没有深入。

依赖

shiro在maven库中的包有许多个,比如shiro-coreshiro-webshiro-spring等,因为我是基于springboot来接入shiro,所以我这里添加的是shiro-spring-boot-web-starter 依赖。

<!-- springboot starter 依赖 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency> 

<!-- shiro 依赖 -->
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring-boot-web-starter</artifactId>
  <version>1.5.3</version>
</dependency>

基础

shiro需要接入用户信息,所以这里写了个简单的service来实现用户信息的查询。

model

package com.martain.shirodemo.model;

public class User {

    private String userName;

    private String password;

    public User(String userName, String password) {
        this.userName = userName;
        this.password = password;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Service

package com.martain.shirodemo.service;

import com.martain.shirodemo.model.User;

/**
 * 用户服务 接口
 * @author martain
 */
public interface IUserService {

    /**
     * 通过用户名来
     * @param userName
     * @return
     */
    User findByUserName(String userName);
}
package com.martain.shirodemo.service.impl;

import com.martain.shirodemo.model.User;
import com.martain.shirodemo.service.IUserService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;

/**
 * 用户服务实现
 * @author martain
 */
@Service
public class UserServiceImpl implements IUserService {

    /**
     * 模拟静态数据库
     */
    private final static Map<String,User> users = new HashMap<>();
    static {
        users.put("haitao",new User("haitao","123456"));
        users.put("martain",new User("martain","1024"));
    }

    /**
     * 通过用户名查询用户
     * @param userName 用户名
     * @return 用户记录
     */
    @Override
    public User findByUserName(String userName) {
        User user = users.get(userName);
        return user;
    }
}

接入shiro

shiro 是非常好用的一个框架,使用他只需要自己添加一些配置即可,如果需要许多的自定义功能只需要简单的重写一些方法或类即可。为了更快的接入,我这里使用了尽可能的少的配置来启动shiro。简单的应用中,shiro的认证原理有如下几个步骤:

  1. 应用代码通过SecurityUtils.getSubject()来获取到subject
  2. subject调用一些方法(比如login等)来委托SecurityManager来进行认证和授权。
  3. SecurityManager通过注入的Realm来获取用户信息来实现认证和授权

自定义Realm

Shiro 认证相关的功能都有SecurityManager 委托给了Realm来实现认证的功能。也可以称他为域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

package com.martain.shirodemo.config;

import com.martain.shirodemo.model.User;
import com.martain.shirodemo.service.IUserService;
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.springframework.beans.factory.annotation.Autowired;

/**
 * 自定义Realm
 * @author martain
 */
public class CustomUserRealm extends AuthorizingRealm {

    @Autowired
    IUserService userService;

    /**
     * 鉴权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("doGetAuthorizationInfo...");
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        return simpleAuthorizationInfo;
    }

    /**
     * 认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException        {
        System.out.println("doGetAuthenticationInfo...");
        // 因为我们在后面调用Subject.login时传入的是UsernamePasswordToken类型 所以 AuthenticationToken 是 UsernamePasswordToken 类型
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        String username = usernamePasswordToken.getUsername();
        String password = String.valueOf(usernamePasswordToken.getPassword());
        User user = userService.findByUserName(username);
        if (user == null){
            throw new AuthenticationException("用户不存在");
        }
        // 这里只是做了简单的比对
        if (!user.getPassword().equalsIgnoreCase(password))
        {
            throw new AuthenticationException("用户密码错误");
        }
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, getName());
        return simpleAuthenticationInfo;
    }
}

编写ShiroConfig

Shiro配置文件中可以配置许多的东西,但不是都是必须的,所以我这里只是添加了必要的配置:注入Realm、注入SecurityManager以及shiroFilterFactoryBean,当然,如果这些没有配置的话程序也是无法启动的。

package com.martain.shirodemo.config;
import com.sun.org.apache.xerces.internal.util.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    /**
     * 注入Realm 
     * @return
     */
    @Bean
    public CustomUserRealm myUserRealm() {
        CustomUserRealm customUserRealm = new CustomUserRealm();
        return customUserRealm;
    }

    /**
     * 注入SecurityManager
     *
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myUserRealm());
        return manager;
    }

    /**
     * Filter 工厂
     * -设置过滤条件以及跳转条件
     *
     * @param securityManager
     * @return
     */
    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        /**
         * anon:匿名用户可访问
         * authc:认证用户可访问ShiroFilterFactoryBean
         * user:使用rememberMe可访问
         * perms:对应权限可访问
         * role:对应角色权限可访问
         **/
        Map<String, String> filterChainMap = new LinkedHashMap<>();
        // 登录接口开放
        filterChainMap.put("/login", "anon");
        // 获取用户信息需要认证用户
        filterChainMap.put("/user/**", "authc");

        // 未授权时的跳转url,比如跳转到登录页面
        bean.setLoginUrl("/login");
        // 首页
        bean.setSuccessUrl("/index");
        // 错误页面
        bean.setUnauthorizedUrl("/error");

        bean.setFilterChainDefinitionMap(filterChainMap);
        return bean;
    }
}

测试接口

package com.martain.shirodemo.controller;

import com.martain.shirodemo.service.IUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
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.RestController;

/**
 * @author martain
 */
@RestController
public class AuthController { 

    @GetMapping("/login")
    public String login(String userName,String password){
        /**
         * 这里的注入的Token类型 就是 出入Realm中doGetAuthenticationInfo的参数类型
         */
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, password);
        Subject subject = SecurityUtils.getSubject();
        try {
            //进行验证,这里可以捕获异常,然后返回对应信息
            subject.login(usernamePasswordToken);
            // 检测角色
            //  subject.checkRole("admin");
            // 检测权限
            //  subject.checkPermissions("query", "add");
        } catch (UnknownAccountException e) {
            return "用户名不存在!";
        } catch (AuthenticationException e) {
            System.out.println(e.getLocalizedMessage());
            return "账号或密码错误!";
        } catch (AuthorizationException e) {
            return "没有权限";
        }

        return "Hello,"+userName;
    }

    @GetMapping("/user/{token}")
    public String getUserInfo(@PathVariable String token){
        return "Hello,your token is "+token;
    }

}

这里添加了两个测试接口,在前面ShiroConfig中已经对/login接口进行了权限的开放以及对 /user接口配置为需要认证。测试过程中,如果直接访问 /user接口是会跳转到 /login接口的,只有在/login接口访问成功之后,才可以成功访问 /user接口。

这个只是按照Shiro的许多默认配置实现的有状态的认证,在实际使用过程中可以使用JWT配合Shiro来实现无状态的认证。

上一篇 下一篇

猜你喜欢

热点阅读