Shiro步步为营--Springboot开启身份验证

项目的完整目录层次如下图所示。

Shiro步步为营--Springboot开启身份验证_第1张图片

项目地址:https://github.com/pengjunlee/shiro-authenticate.git 

添加依赖与配置

在本工程中,Shiro使用了Ehchache作缓存,因此需要在工程POM文件中引入它们的Maven依赖。

	
		org.springframework.boot
		spring-boot-starter-parent
		2.1.0.RELEASE
		
	

	
		UTF-8
	

	

		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-thymeleaf
		

		
		
			org.apache.shiro
			shiro-core
			1.4.1
		
		
			org.apache.shiro
			shiro-spring
			1.4.1
		
		
			org.slf4j
			slf4j-log4j12
			test
		
		
			commons-logging
			commons-logging
			1.2
		

		
		
			org.apache.shiro
			shiro-ehcache
			
				
					net.sf.ehcache
					ehcache-core
				
			
			1.4.0
		
		
			org.springframework.boot
			spring-boot-starter-cache
		
		
			net.sf.ehcache
			ehcache
		
	

EhCacheManager使用默认的 classpath:/ehcache.xml 加载配置,文件内容如下。



	
	
	

在application.properties核心配置文件中加入Thymeleaf相关配置,方便前端页面演示:

# thymelea模板配置
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
# 热部署文件,页面不产生缓存,及时更新
spring.thymeleaf.cache=false
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**

创建用户实体类


import java.io.Serializable;

public class UserEntity implements Serializable {

	private static final long serialVersionUID = 1L;

	private Long id; // 主键ID

	private String name; // 登录用户名

	private String password; // 登录密码

	private String nickName; // 昵称

	private Boolean locked; // 账户是否被锁定

	public UserEntity() {
		super();
	}

	public UserEntity(Long id, String name, String password, String nickName, Boolean locked) {
		super();
		this.id = id;
		this.name = name;
		this.password = password;
		this.nickName = nickName;
		this.locked = locked;
	}

	// 此处省略各属性的 getXXX() 和 setXXX() 方法

}

自定义Realm

Shiro步步为营--Springboot开启身份验证_第2张图片

如果你只是想使用Shiro 进行身份验证,而不需要它的授权功能。那么,你只需让你的Realm继承 AuthenticatingRealm 并实现其抽象方法 doGetAuthenticationInfo() 即可。

import java.util.HashMap;
import java.util.Map;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.realm.AuthenticatingRealm;

import com.pengjunlee.domain.UserEntity;

/**
 * 只开启身份验证,继承 AuthenticatingRealm 并实现 doGetAuthenticationInfo() 方法即可
 */
public class LoginRealm extends AuthenticatingRealm {

	private static Map users = new HashMap(16);

	static {
		UserEntity user1 = new UserEntity(1L, "graython", "123456", "灰先生", false);
		UserEntity user2 = new UserEntity(2L, "plum", "123456", "李先生", false);
		UserEntity user3 = new UserEntity(3L, "nightking", "123456", "夜王", false);
		UserEntity user4 = new UserEntity(4L, "guomeimei", "123456", "郭妹妹", true);

		users.put("graython", user1);
		users.put("plum", user2);
		users.put("nightking", user3);
		users.put("guomeimei", user4);
	}

	/**
	 * 查询数据库,将获取到的用户安全数据封装返回
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		// 从 AuthenticationToken 中获取当前用户
		String username = (String) token.getPrincipal();
		// 查询数据库获取用户信息,此处使用 Map 来模拟数据库
		UserEntity user = users.get(username);

		// 用户不存在
		if (user == null) {
			throw new UnknownAccountException("用户不存在!");
		}

		// 用户被锁定
		if (user.getLocked()) {
			throw new LockedAccountException("该用户已被锁定,暂时无法登录!");
		}

		/**
		 * 将获取到的用户数据封装成 AuthenticationInfo 对象返回,此处封装为 SimpleAuthenticationInfo
		 * 对象。
		 *  参数1. 认证的实体信息,可以是从数据库中获取到的用户实体类对象或者用户名 
		 *  参数2. 查询获取到的登录密码 
		 *  参数3. 当前  Realm 对象的名称,直接调用父类的 getName() 方法即可
		 */
		SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
		return info;
	}

}

在自定义Realm中,我们可以对一些业务相关的用户身份验证异常进行处理。除了可以直接使用 Shiro 为我们提供好的下面这些身份验证异常类,我们也可以使用其他自定义的验证异常类。

Shiro步步为营--Springboot开启身份验证_第3张图片

集成Shiro

import java.util.LinkedHashMap;

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
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.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import net.sf.ehcache.CacheManager;

@Configuration
public class ShiroConfig {

	/**
	 * 交由 Spring 来自动地管理 Shiro-Bean 的生命周期
	 */
	@Bean
	public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	/**
	 * 配置访问资源需要的权限
	 */
	@Bean
	ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		shiroFilterFactoryBean.setLoginUrl("/login");
		shiroFilterFactoryBean.setSuccessUrl("/authorized");
		shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
		LinkedHashMap filterChainDefinitionMap = new LinkedHashMap();
		filterChainDefinitionMap.put("/login", "anon"); // 可匿名访问
		filterChainDefinitionMap.put("/logout", "logout"); // 退出登录
		filterChainDefinitionMap.put("/**", "authc"); // 需登录才能访问
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}

	/**
	 * 配置 SecurityManager,通常需要配置以下属性: 
	 * 1.CacheManager 
	 * 2.Realm 
	 * 3.SessionManager
	 */
	@Bean
	public SecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		// 1.CacheManager
		securityManager.setCacheManager(ehCacheManager());
		// 2.Realm
		securityManager.setRealm(loginRealm());
		// 3.SessionManager
		securityManager.setSessionManager(sessionManager());
		return securityManager;
	}

	/**
	 * EhCacheManager缓存配置,默认使用 classpath:/ehcache.xml
	 */
	@Bean("cacheManager")
	public EhCacheManager ehCacheManager() {
		EhCacheManager em = new EhCacheManager();
		em.setCacheManager(cacheManager());
		return em;
	}

	@Bean("cacheManager2")
	CacheManager cacheManager() {
		return CacheManager.create();
	}

	/**
	 * Realm配置,需实现 Realm 接口
	 */
	@Bean
	LoginRealm loginRealm() {
		LoginRealm loginRealm = new LoginRealm();
		return loginRealm;
	}

	/**
	 * SessionManager配置
	 */
	@Bean
	public DefaultWebSessionManager sessionManager() {
		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
		sessionManager.setGlobalSessionTimeout(1800 * 1000);
		sessionManager.setDeleteInvalidSessions(true);
		sessionManager.setSessionValidationSchedulerEnabled(true);
		return sessionManager;
	}
}
public enum DefaultFilter {

    anon(AnonymousFilter.class), // 可以匿名访问,示例:/static/**=anon
    authc(FormAuthenticationFilter.class), // 需要经过身份验证才能访问,示例:/**=authc
    logout(LogoutFilter.class), // 退出登录,示例:/logout=logout
    perms(PermissionsAuthorizationFilter.class), // 需要用户具有相应权限才能访问,示例:/user/**=perms["user:*"]
    port(PortFilter.class), // 只能通过指定端口访问,端口错误会重定向到相应端口,,示例:/test=port[80]
    rest(HttpMethodPermissionFilter.class), // rest风格拦截器,会自动根据请求方法构建权限字符串
    roles(RolesAuthorizationFilter.class), // 需要用户具有相应角色才能访问,示例:/admin/**=roles[admin]
    ssl(SslFilter.class), // 只能通过HTTPS协议访问,否则自动跳转回HTTPS端口(443),示例:/admin/**=ssl
    user(UserFilter.class); // 用户通过身份验证或者记住我登录后都能访问,示例:/admin/**=user
	
}

视图Controller

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.pengjunlee.domain.UserEntity;

@Controller
public class LoginController {

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

	@PostMapping(value = "/login")
	public String userLogin(@RequestParam(name = "username") String userName,
			@RequestParam(name = "password") String password, ModelMap model) {

		// 获取当前用户主体
		Subject subject = SecurityUtils.getSubject();
		String msg = null;
		// 判断是否已经验证身份,即是否已经登录
		if (!subject.isAuthenticated()) {
			// 将用户名和密码封装成 UsernamePasswordToken 对象
			UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
			try {
				subject.login(token);
				System.out.println("用户 [ " + userName + " ] 登录成功。");
				addUserInfo2Model(model);

			} catch (UnknownAccountException uae) { // 账号不存在
				msg = "用户名与密码不匹配,请检查后重新输入!";
			} catch (IncorrectCredentialsException ice) { // 账号与密码不匹配
				msg = "用户名与密码不匹配,请检查后重新输入!";
			} catch (LockedAccountException lae) { // 账号已被锁定
				msg = "该账户已被锁定,如需解锁请联系管理员!";
			} catch (AuthenticationException ae) { // 其他身份验证异常
				msg = "登录异常,请联系管理员!";
			}
		}

		if (subject.isAuthenticated()) {
			return "redirect:/authorized";
		} else {
			model.addAttribute("msg", msg);
			return "403";
		}

	}

	@GetMapping("/logout")
	public String logout() {
		return "redirect:/login";
	}

	@GetMapping("/unauthorized")
	public String unauthorized(ModelMap model) {
		return "403";
	}

	@GetMapping("/authorized")
	public String authorized(ModelMap model) {
		addUserInfo2Model(model);
		return "success";
	}

	// 将用户信息添加到 model
	private void addUserInfo2Model(ModelMap model) {
		Subject subject = SecurityUtils.getSubject();
		UserEntity currentUser = (UserEntity) subject.getPrincipal();
		model.addAttribute("user", currentUser);
	}
}

前端页面

login.html

Shiro步步为营--Springboot开启身份验证_第4张图片

完整代码:








	

账号:

密码:

success.html







	

,欢迎您!

退出登录

403.html







	

您无权访问该页面,请先登录!

去登录

登录测试

账号与密码均正确

使用 graython 账号登录,输入正确的密码,登录成功。 

Shiro步步为营--Springboot开启身份验证_第5张图片

账号不存在

随便输入一个不存在的账号进行登录,登录失败。

Shiro步步为营--Springboot开启身份验证_第6张图片

密码错误

使用 graython 账号登录,输入错误的密码,登录失败。 

Shiro步步为营--Springboot开启身份验证_第7张图片

账号被锁定

Shiro步步为营--Springboot开启身份验证_第8张图片

注意:由于启用了Shiro缓存,用户成功登录之后,在不退出登录的情况下,再次请求登录页面重新登录该用户,此时,无论密码是否输入正确都会登录成功。

你可能感兴趣的:(安全框架,shiro,身份验证,登录)