springboot学习笔记3:重识shiro

在之前ssm框架阶段,学习过shiro的一些基本使用,当时使用shiro是这样的:

1.配置shiro的配置文件,使用spring管理shiro

2.编辑登录的realm,并在内部实现登录方法

3.在controller层将用户名及密码封装成一个UsernamePasswordToken令牌,并实现登录方法,如图:

当时我们将封装令牌及登录方法写入了controller中,但是这种编辑方式是错误的,重新学习shiro后,对shiro有了新的认识:

shiro的核心是过滤器,因此,如果没有登录的话, 应该直接拦截请求,而不是到后台在处理是否登录,在之前学习种,realm是这样写的:

这是之前的认证方法,当时认为是controller层调用登录方法后,然后再进入realm种,进行账号密码的比较,然后判断是否认证成功,当然,这是错误的理解,下面重新认识:

首先写一个控制器:

@Controller
public class LoginController {

    @RequestMapping("/login")
    public String login() {
        return "login";
    }

    @RequestMapping("/")
    public String home() {
        return "index";
    }
}

可以看到又两个路径,/login路径返回登录页面,/返回主页面(为什么这么写,因为shiro在认证后,如果登录,那么会将请求发送到根目录,后面验证)

然后编辑登录的realm:

package com.zs.springboot.realm;

import com.zs.springboot.model.User;
import com.zs.springboot.service.UserService;
import org.apache.shiro.SecurityUtils;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Map;

public class LoginRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;
    /**
     * 授权方法
     * @param principal
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {

        return null;
    }

    /**
     * 认证方法
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取当前登录的用户名
        String username = (String) token.getPrincipal();
        //根据用户到数据库搜索用户信息
        Map<String, Object> login = userService.login(username);
        System.out.println(login);
        //如果用户不存在则抛出异常
        if ((Integer) login.get("code") == 404) {
            throw new UnknownAccountException("用户不存在");
        }
        //如果用户存在,获取用户信息
        User user = (User) login.get("user");
        //进行认证
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName());
        //将用户信息放入session中,密码制空
        Session session = SecurityUtils.getSubject().getSession();
        user.setPassword(null);
        session.setAttribute("user", user);
        return info;
    }
}
View Code

然后再springboot中使用Java类的方式配置shiro

package com.zs.springboot.config;

import com.zs.springboot.realm.LoginRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 创建shiro配置类
 */
@SpringBootConfiguration
public class ShiroConfig {

    /**
     * 将shiro的生命周期交给spring容器管理,相当于:
     * <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return  new LifecycleBeanPostProcessor();
    }

    /**
     * 密码加密
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        hashedCredentialsMatcher.setHashIterations(1024);
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }

    /**
     * 配置shiro的缓存管理器
     * @return
     */
    @Bean
    public EhCacheManager ehCacheManager() {
        EhCacheManager ehCacheManager = new EhCacheManager();
        return ehCacheManager;
    }

    /**
     * 配置自己的realm
     * @return
     */
    @Bean
    public LoginRealm loginRealm() {
        LoginRealm loginRealm = new LoginRealm();
        loginRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        /*在开发阶段不需要缓存*/
        //loginRealm.setCacheManager(ehCacheManager());
        return loginRealm;
    }
    /**
     * 创建shiro的安全管理器
     * @return
     */
    @Bean(name="securityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager() {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setCacheManager(ehCacheManager());
        defaultWebSecurityManager.setRealm(loginRealm());
        return defaultWebSecurityManager;
    }

    /**
     * 核心:配置shiro的默认的过滤器
     */
    @Bean(name="shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
        filter.setSecurityManager(defaultWebSecurityManager());
        filter.setLoginUrl("/login");
//        filter.setSuccessUrl("/index");
        filter.setUnauthorizedUrl("/404");
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/logout", "logout");
        /**
         *   *和**的区别
         *   假如有一个包:com.zs.service
         *   这个包下有service的接口,然后包里又有一个包为:impl,那么这个包的路径就是com.zs.service.impl
         *   这是如果扫描包:com.zs.service/* 那么就只表示当前目录service下的接口,不包括impl包内的类
         *   如果扫描:com.zs.service/** 表示扫描service接口下的所有东西,包括impl包内的类
         *   *:只表示当前目录的子目录(一级)
         *   **:表示当前目录下的所有目录
         */
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        filter.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return filter;
    }

    /**
     * @ConditionalOnMissingBean:条件注解
     * 当找不到某一个bean的时候才会被加载
     * springboot源码中拥有DefaultAdvisorAutoProxyCreator 的bean的配置,
     * 这时如果自己在配置一个,启动加载配置时就会加载到两个一样的bean,就会冲突报错!
     * 添加条件注解后,只有当springboot自带的bean无法被加载到时,才会加载自己配置的bean信息
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaltAdverisor= new DefaultAdvisorAutoProxyCreator();
        /*通过动态代理创建出shiro的代理对象,true代表是cglib代理*/
        defaltAdverisor.setProxyTargetClass(true);
        return defaltAdverisor;
    }

    /**
     * AuthorizationAttributeSourceAdvisor
     * 授权源适配器,源数据
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(defaultWebSecurityManager());
        return advisor;
    }



}
View Code

登录页面:

<form action="login" method="post">
    <input type="text" name="username"/>
    <input type="password" name="password"/>
    <input type="submit"/>
</form>

运行入口类,登录测试(数据库的密码应为加密过的字符串)

shiro运行原理:

我们第一次打开浏览器,发送login请求,然后shiro会拦截这个请求,查看请求是否带有参数,如果没有带参数,说明这是第一次登录,需要跳转到登录页面,然后返回登录页面,

在登陆页面输入账号和密码后,点击登录,再次发送login请求,shrio再次拦截,检查到参数(用户名和密码),表示用户想要登录,然后shiro会检测用户是否已经认证,如果已经认证,直接放行,如果没有认证,则进入loginRealm中进行认证,再认证方法中,通过用户名查看是否存在用户,如果存在则获取用户的信息,然后将用户的信息放入simpleAuthencationInfo对象中,由shiro来判断前端发送的用户信息与数据库取到的用户信息是否匹配,用一个图来帮助理解:

 关于授权:

index页面:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
    <!--
    xmlns:shiro="http://www.pollix.at/thymeleaf/shiro:
      和jsp中jstl很像
      c:xxxxxxx prefix="c"
      都是使用Java语言编写的html模板,使用的规则也是一样的
      如果现在需要使用shiro的标签,需要在html头中方法上面信息,相当于在jsp中添加了jstl的标签
    -->
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
<h1>hello world</h1>
<!--
    游客模式:所有的人都可以查看,相当于匿名anon
-->
<shiro:guest>
    用户你好
</shiro:guest>
<!--在认证阶段:SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName())
    如果第一个参数传递的是user.getUsername(),那么<shiro:principal/>标签展示的就是用户名,如果是对象user,就展示整个对象的信息,因此在认证
    阶段,登录成功后会将密码制空
-->
<shiro:principal/>

<!--当登录的用户拥有某个角色时,就展示标签内的内容,否则不展示,当使用这个标签时,需要到后台授权器进行查询是否拥有该角色,可以在授权器中打印一句话
    验证该过程,只有当跳转该页面时,页面加载阶段加载到该标签,才会跳到后台授权器
-->
<shiro:hasRole name="admin">
    图书管理员
</shiro:hasRole>
</body>
</html>

要想使用标签库,需要在ShiroConfig配置文件中添加shiro的标签库支持:

原文地址:https://www.cnblogs.com/Zs-book1/p/11373861.html