Spring Boot 安全框架 Shiro 入门

 

 

参考地址:http://www.iocoder.cn/Spring-Boot/Shiro/

《认证 (authentication) 和授权 (authorization) 的区别》

打飞机举例子:

  • 【认证】你要登机,你需要出示你的 passport 和 ticket,passport 是为了证明你张三确实是你张三,这就是 authentication。
  • 【授权】而机票是为了证明你张三确实买了票可以上飞机,这就是 authorization。

论坛举例子:

  • 【认证】你要登录论坛,输入用户名张三,密码 1234,密码正确,证明你张三确实是张三,这就是 authentication。
  • 【授权】再一 check 用户张三是个版主,所以有权限加精删别人帖,这就是 authorization 。

简单来说:认证解决“你是谁”的问题,授权解决“你能做什么”的问题

pom

    <dependencies>
        <!-- 实现对 Spring MVC 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 实现对 Shiro 的自动化配置 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>
    </dependencies>

配置shiro

package cn.iocoder.springboot.lab01.shirodemo.config;

import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.SimpleAccountRealm;
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 {
    /**
     * SimpleAccountRealm 是使用内存作为数据源,我们可以手动往里面添加用户、角色、权限等数据。
     * @return
     */
    @Bean
    public Realm realm() {//作用:身份验证和授权
        // 创建 SimpleAccountRealm 对象
        SimpleAccountRealm realm = new SimpleAccountRealm();
        // 添加两个用户。参数分别是 username、password、roles 。
        realm.addAccount("admin", "admin", "ADMIN");
        realm.addAccount("normal", "normal", "NORMAL");
        return realm;
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        // 创建 DefaultWebSecurityManager 对象
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置其使用的 Realm
        securityManager.setRealm(this.realm());
        return securityManager;
    }

    /**
     * #setLoginUrl(String loginUrl) 方法,设置登录 URL 。在 Shiro 中,约定 GET loginUrl 为登录页面,POST loginUrl 为登录请求。
     * #setSuccessUrl(String successUrl) 方法,设置登录成功 URL 。在登录成功时,会重定向到该 URL 上。
     * #etUnauthorizedUrl(String unauthorizedUrl) 方法,设置无权限的 URL 。在请求校验权限不通过时,会重定向到该 URL 上
     * 上述的 URL 对应的接口,都需要我们自己来实现
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        // 创建 ShiroFilterFactoryBean 对象,用于创建 ShiroFilter 过滤器
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();

        // 设置 SecurityManager
        filterFactoryBean.setSecurityManager(this.securityManager());

        // 设置 URL 们
        filterFactoryBean.setLoginUrl("/login"); // 登陆 URL
        filterFactoryBean.setSuccessUrl("/login_success"); // 登陆成功 URL
        filterFactoryBean.setUnauthorizedUrl("/unauthorized"); // 无权限 URL

        // 设置 URL 的权限配置
        filterFactoryBean.setFilterChainDefinitionMap(this.filterChainDefinitionMap());

        return filterFactoryBean;
    }

    /**
     * 比较常用的过来器有:
     *
     *     anon :AnonymousFilter :允许匿名访问,即无需登录。
     *     authc :FormAuthenticationFilter :需要经过认证的用户,才可以访问。如果是匿名用户,则根据 URL 不同,会有不同的处理:
     *         如果拦截的 URL 是 GET loginUrl 登录页面,则进行该请求,跳转到登录页面。
     *         如果拦截的 URL 是 POST loginUrl 登录请求,则基于请求表单的 username、password 进行认证。认证通过后,默认重定向到 GET loginSuccessUrl 地址。
     *         如果拦截的 URL 是其它 URL 时,则记录该 URL 到 Session 中。在用户登录成功后,重定向到该 URL 上。
     *     logout :LogoutFilter :拦截的 URL ,执行退出操作。退出完成后,重定向到 GET loginUrl 登录页面。
     *     roles :RolesAuthorizationFilter :拥有指定角色的用户可访问。
     *     perms :PermissionsAuthorizationFilter :拥有指定权限的用户可以访问
     * @return
     */
    private Map<String, String> filterChainDefinitionMap() {
        Map<String, String> filterMap = new LinkedHashMap<>(); // 注意要使用有序的 LinkedHashMap ,顺序匹配
        filterMap.put("/test/demo", "anon"); // 允许匿名访问
        filterMap.put("/test/admin", "roles[ADMIN]"); // 需要 ADMIN 角色
        filterMap.put("/test/normal", "roles[NORMAL]"); // 需要 NORMAL 角色
        filterMap.put("/logout", "logout"); // 退出
        filterMap.put("/**", "authc"); // 默认剩余的 URL ,需要经过认证
        return filterMap;
    }

}

controller

package cn.iocoder.springboot.lab01.shirodemo.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

@Controller
@RequestMapping("/")
public class SecurityController {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @GetMapping("/login")
    public String loginPage() {
        return "login.html";
    }
    /**
     * 对于登录请求,会被我们配置的 Shiro FormAuthenticationFilter 过滤器进行拦截,进行用户的身份认证。整个过程如下:
     *
     *     FormAuthenticationFilter 解析请求的 username、password 参数,创建 UsernamePasswordToken 对象。
     *     然后,调用 SecurityManager 的 #login(Subject subject, AuthenticationToken authenticationToken) 方法,执行登录操作,进行“身份验证”(认证)。
     *     在这内部中,调用 Realm 的 #getAuthenticationInfo(AuthenticationToken token) 方法,进行认证。此时,根据认证的是否成功,会有不同的处理:
     *         如果认证通过,则 FormAuthenticationFilter 会将请求重定向到 GET loginSuccess 地址上。
     *         【重要】如果认证失败,则会将认证失败的原因设置到请求的 attributes 中,后续该请求会继续请求到 POST login 地址上。这样,在 POST loginUrl 地址上,我们可以从 attributes 中获取到失败的原因,提示给用户。
     *
     * 所以,POST loginUrl 的目的,实际是为了处理认真失败的情况
     */
    @ResponseBody
    @PostMapping("/login")
    public String login(HttpServletRequest request) {
        // 判断是否已经登陆
        Subject subject = SecurityUtils.getSubject();
        if (subject.getPrincipal() != null) {
            return "你已经登陆账号:" + subject.getPrincipal();
        }

        // 获得登陆失败的原因
        String shiroLoginFailure = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
        // 翻译成人类看的懂的提示
        String msg = "";
        if (UnknownAccountException.class.getName().equals(shiroLoginFailure)) {
            msg = "账号不存在";
        } else if (IncorrectCredentialsException.class.getName().equals(shiroLoginFailure)) {
            msg = "密码不正确";
        } else if (LockedAccountException.class.getName().equals(shiroLoginFailure)) {
            msg = "账号被锁定";
        } else if (ExpiredCredentialsException.class.getName().equals(shiroLoginFailure)) {
            msg = "账号已过期";
        } else {
            msg = "未知";
            logger.error("[login][未知登陆错误:{}]", shiroLoginFailure);
        }
        return "登陆失败,原因:" + msg;
    }

    @ResponseBody
    @GetMapping("/login_success")
    public String loginSuccess() {
        return "登陆成功";
    }

    @ResponseBody
    @GetMapping("/unauthorized")
    public String unauthorized() {
        return "你没有权限";
    }

}

测试controller

package cn.iocoder.springboot.lab01.shirodemo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 权限测试
 */
@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/demo")
    public String demo() {
        return "示例返回";
    }

    @GetMapping("/home")
    public String home() {
        return "我是首页";
    }

    @GetMapping("/admin")
    public String admin() {
        return "我是管理员";
    }

    @GetMapping("/normal")
    public String normal() {
        return "我是普通用户";
    }

}

启动类

package cn.iocoder.springboot.lab01.shirodemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

使用注解方式

package cn.iocoder.springboot.lab01.shirodemo.controller;

import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/demo")
public class DemoController {
    /**
     * 验证不起作用,暂时无法解决
     * @return
     */
    @RequiresGuest
    @GetMapping("/echo")
    public String demo() {
        return "示例返回";
    }

    @RequiresGuest
    @GetMapping("/aaa")
    public String aaa() {
        return "aaa示例返回";
    }

    @GetMapping("/home")
    public String home() {
        return "我是首页";
    }

    @RequiresRoles("ADMIN")
    @GetMapping("/admin")
    public String admin() {
        return "我是管理员";
    }

    @RequiresRoles("NORMAL")
    @GetMapping("/normal")
    public String normal() {
        return "我是普通用户";
    }

}

3.1 @RequiresGuest

@RequiresGuest 注解,和 anon 等价。

3.2 @RequiresAuthentication

@RequiresAuthentication 注解,和 authc 等价。

3.3 @RequiresUser

@RequiresUser 注解,和 user 等价,要求必须登录。

3.4 @RequiresRoles

@RequiresRoles 注解,和 roles 等价。

// 属于 NORMAL 角色
@RequiresRoles("NORMAL")

// 要同时拥有 ADMIN 和 NORMAL 角色
@RequiresRoles({"ADMIN", "NORMAL"})

// 拥有 ADMIN 或 NORMAL 任一角色即可
@RequiresRoles(value = {"ADMIN", "NORMAL"}, logical = Logical.OR)

3.5 @RequiresPermissions

@RequiresPermissions 注解,和 perms 等价

// 拥有 user:add 权限
@RequiresPermissions("user:add")

// 要同时拥有 user:add 和 user:update 权限
@RequiresPermissions({"user:add", "user:update"})

// 拥有 user:add 和 user:update 任一权限即可
@RequiresPermissions(value = {"user:add", "user:update"}, logical = Logical.OR)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值