在之前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; } }
然后再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; } }
登录页面:
<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来判断前端发送的用户信息与数据库取到的用户信息是否匹配,用一个图来帮助理解:
原文:https://www.cnblogs.com/Zs-book1/p/11373861.html