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来判断前端发送的用户信息与数据库取到的用户信息是否匹配,用一个图来帮助理解:

猜你喜欢

转载自www.cnblogs.com/Zs-book1/p/11373861.html