servlet3.0全注解spring整合shiro

Posted 巅峰小学生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了servlet3.0全注解spring整合shiro相关的知识,希望对你有一定的参考价值。

基本说明

基于Servlet3.0全注解配置的Spring整合Shiro

目录

配置文件

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.td</groupId>
	<artifactId>spring-shrio</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<dependencies>
		<!-- servlet 3.0 -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>jsp-api</artifactId>
			<version>2.2</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>

		<!-- spring mvc -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>4.3.14.RELEASE</version>
		</dependency>

		<!-- shiro -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.4.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-web</artifactId>
			<version>1.4.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>
		<dependency>
		    <groupId>org.apache.shiro</groupId>
		    <artifactId>shiro-ehcache</artifactId>
		    <version>1.4.0</version>
		</dependency>
		<dependency><!-- 注意:ehcahe 2.5 EhCacheManager以上使用了单例,创建多次会报错 -->
		    <groupId>net.sf.ehcache</groupId>
		    <artifactId>ehcache-core</artifactId>
		    <version>2.4.8</version>
		</dependency>
		
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.5</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<!-- 编译插件 -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.0</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<!-- web项目插件 -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>3.1.0</version>
			</plugin>
			<!-- tomcat7插件 -->
			<plugin>
				<groupId>org.apache.tomcat.maven</groupId>
				<artifactId>tomcat7-maven-plugin</artifactId>
				<version>2.2</version>
				<configuration>
					<port>8888</port>
					<path>/</path>
					<uriEncoding>UTF-8</uriEncoding>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

javax.servlet.ServletContainerInitializer(servlet 3.0配置,容器加载配置)

com.td.WebServletContainerInitializer

WebServletContainerInitializer.java (相当于web.xml)

package com.td;

import java.util.EnumSet;
import java.util.Set;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.filter.DelegatingFilterProxy;

/**
 * web.xml配置文件
 */
@HandlesTypes(WebApplicationInitializer.class)
public class WebServletContainerInitializer implements ServletContainerInitializer {

	@Override
	public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
		
		// shiro过滤器
		Dynamic filter = ctx.addFilter("shiroFilter", DelegatingFilterProxy.class);
		filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
		filter.setInitParameter("targetFilterLifecycle", "true");
		
	}

}

MyWebAppInitializer.java (相当于spring.xml)

package com.td;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import com.td.configuration.RootConfig;
import com.td.configuration.WebConfig;

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	// 父容器
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    // 子容器
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebConfig.class };
    }

    // 拦截请求配置
    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

模拟数据库表(基于角色控制权限)

Permission.java

package com.td.enums;

public enum Permission {
	
	select("select"),update("update"),delete("delete"),insert("insert");
	
	String permissionName;
	private Permission(String permissionName) {
		this.permissionName = permissionName;
	}
	public String getPermissionName() {
		return permissionName;
	}
	public void setPermissionName(String permissionName) {
		this.permissionName = permissionName;
	}
}

Role.java

package com.td.enums;

public enum Role {

	//管理员角色有 CRUD权限
	admin("admin", Permission.select,Permission.update,Permission.delete,Permission.insert),
	//普通用户只有查看权限
	user("guest", Permission.select);
	
	String roleName;
	Permission[] permission;
	Role(String roleName, Permission... permission){
		this.roleName = roleName;
		this.permission = permission;
	}
	
	public String getRoleName() {
		return roleName;
	}
	public void setRoleName(String roleName) {
		this.roleName = roleName;
	}
	public Permission[] getPermission() {
		return permission;
	}
	public void setPermission(Permission[] permission) {
		this.permission = permission;
	}
}

User.java

package com.td.enums;

public enum User {

	admin(Role.admin, "tandi", "tandi"),  //管理员账号
	guest(Role.user, "lisi", "lisi"); //普通用户账号

	Role role;
	String username;
	String password;

	User(Role role, String username, String password) {
		this.role = role;
		this.username = username;
		this.password = password;
	}

	
	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public Role getRole() {
		return role;
	}

	public void setRole(Role role) {
		this.role = role;
	}

}

父子容器配置

RootConfig.java

package com.td.configuration;

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

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

import com.td.filter.UserFormAuthenticationFilter;
import com.td.realm.UserAuthorizingRealm;

@Configuration
@ComponentScan(basePackages="com.td",
includeFilters=@Filter(type=FilterType.ANNOTATION,classes=Controller.class))
public class RootConfig {

	/** shiro bean */
	
//	@Bean
//	public EhCacheManager ehcacheManager() {
//		EhCacheManager bean = new EhCacheManager();
//		bean.setCacheManagerConfigFile("classpath:ehcache.xml");
//		return bean;
//	}
	
	@Bean
	public org.apache.shiro.mgt.SecurityManager securityManager(
			UserAuthorizingRealm userAuthorizingRealm/*, 
			EhCacheManager ehCacheManager*/) {
		
		DefaultWebSecurityManager bean = new DefaultWebSecurityManager();
		
		bean.setRealm(userAuthorizingRealm); //自定义realm
//		bean.setCacheManager(ehCacheManager);
//		bean.setSessionManager(sessionManager);
		
		return bean;
	}	
	
	@Bean
	public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager, 
			UserFormAuthenticationFilter userFormAuthenticationFilter, 
			LogoutFilter logoutFilter) {
		
		ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
		
		bean.setSecurityManager(securityManager);
		bean.setLoginUrl("/user/login"); //表单登录URL
		bean.setSuccessUrl("/login_success"); //认证成功跳转到的URL
		bean.setUnauthorizedUrl("/login"); //受权失败调转到的URL
		
		//自定义拦截器
		Map<String, javax.servlet.Filter> filters = new HashMap<>();
		filters.put("formFilter", userFormAuthenticationFilter); 
		filters.put("userLogout", logoutFilter); 
		bean.setFilters(filters);
		
		//url拦截配置
		Map<String, String> map = new HashMap<>();
		map.put("/", "anon");
		map.put("/index", "anon");
		map.put("/login", "anon"); //登录页面,不能省略该配置
		map.put("/user/logout", "userLogout"); //登出
		
//		map.put("/user/login", "formFilter"); //登录认证
		map.put("/**", "formFilter");
		bean.setFilterChainDefinitionMap(map);
		
		return bean;
	}	
	
	@Bean
	public LogoutFilter logoutFilter() {
		LogoutFilter bean = new LogoutFilter();
		bean.setRedirectUrl("/login");
		return bean;
	}
	

}

WebConfig.java

package com.td.configuration;

import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@ComponentScan(basePackages="com.td",
includeFilters=@Filter(type=FilterType.ANNOTATION,classes=Controller.class),useDefaultFilters=false)
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
	
	//配置视图解析器
	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
		registry.jsp("/", ".jsp");
	}
	
	//静态资源配置
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/res/**")
            .addResourceLocations("/img/") 
            .setCachePeriod(31556926);
    }
    

	/**
	 * 启动 shiro 注解
	 *  【注意】
	 *  1. 因为配置的是 父子 容器,所以如果要在@Controller标注的类下使用shiro注解
	 *  	必须将以下配置配置到子容器中来,否者父容器初始化时只扫描shiro注解而不扫描@Controller
	 *  	就会出现shiro注解不生效的情况;
	 *  
	 *  2.所以如果在@Controller标注的类下使用shiro注解,必须是@Controller和shiro注解同时被扫描才是
	 * @return
	 */
	@Bean
	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}
	
	@Bean
	@DependsOn("lifecycleBeanPostProcessor")
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator bean = new DefaultAdvisorAutoProxyCreator();
		bean.setProxyTargetClass(true);
		return bean;
	}

	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor bean = new AuthorizationAttributeSourceAdvisor();
		bean.setSecurityManager(securityManager);
		return bean;
	}
	
}

自定义认证受权Reaml

UserAuthorizingRealm.java

package com.td.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;

import com.td.enums.Permission;
import com.td.enums.User;

@Component
public class UserAuthorizingRealm extends AuthorizingRealm {
	
//	@Autowired
//	XxxService service;

	/**
	 * 认证
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		
		System.out.println("\\r\\n ===reaml认证处理===");
		
		UsernamePasswordToken upToken = (UsernamePasswordToken)token;
		String username = upToken.getUsername();
		String password = new String(upToken.getPassword());

		/** 模拟从数据库根据用户名称获取对应的user */
		User loginUser = null;
		if(User.admin.getUsername().equals(username)) {
			loginUser = User.admin;
		}else if(User.guest.getUsername().equals(username)) {
			loginUser = User.guest;
		}else {
			throw new UnknownAccountException("认证失败!!!没有改账号。。。。");
		}
		
		System.out.println(username + ":::" + password);
		//校验密码
		if(!loginUser.getPassword().equals(password))
			throw new IncorrectCredentialsException("认证失败!!!密码错误。。。。");
		
		//返回认证信息
		return new SimpleAuthenticationInfo(username, password, getName());
	}



	/**
	 * 受权
	 * 注意:受权方法是当前用户访问权限资源的时候才会调用的
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		
		System.out.println("\\r\\n ===reaml受权处理===");
		
		//获取在认证过程中传入的“主要认证信息”
		String username = (String) getAvailablePrincipal(principals);
		
		/** 模拟从数据库根据用户名称获取对应的user */
		User loginUser = null;
		if(User.admin.getUsername().equals(username)) {
			loginUser = User.admin;
		}else if(User.guest.getUsername().equals(username)) {
			loginUser = User.guest;
		}
		
		// 创建受权信息对象
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		
		// 设置当前用户的角色
		System.out.println("当前用户有以下角色");
		System.out.println("-----" + loginUser.getRole().getRoleName() + "-----");
		info.addRole(loginUser.getRole().getRoleName()); // amdin 和 guest
		
		// 设置当前用户的权限
		Permission[] permissions = loginUser.getRole().getPermission(); // crud四个权限
		System.out.println("当前用户有以下权限");
		for (Permission perm : permissions) {
			System.out.println("-----" + perm.getPermissionName() + "-----");
			info.addStringPermission(perm.getPermissionName());
		}
		
		return info;
	}


}

登录表单过滤器

UserFormAuthenticationFilter.java

package com.td.filter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.springframework.stereotype.Component;

@Component
public class UserFormAuthenticationFilter extends FormAuthenticationFilter {

	@Override
	protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
		
		System.out.println("filter拦截了请求,创建token....");
		
		//拦截获取登录的账号和密码
		String username = getUsername(request);
		String password = getPassword(request);
		if (password==null){
			password = "";
		}
		
		//返回UsernamePasswordToken对象
		return new UsernamePasswordToken(username, password);
	}
	
	//认证成功
	@Override
	protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
		System.out.println("filter认证成功方法执行...");
		super.issueSuccessRedirect(request, response);
	}


	//认证失败
	@Override
	protected boolean onLoginFailure(AuthenticationToken token,
			AuthenticationException e, ServletRequest request, ServletResponse response) {
		
		System.out.println("filter认证失败方法执行...");
		
		String className = e.getClass().getName(), message = "";
		
		if (IncorrectCredentialsException.class.getName().equals(className)
				|| UnknownAccountException.class.getName().equals(className)){
			message = "用户或密码错误, 请重试.";
		}
		else if (e.getMessage() != null && StringUtils.startsWith(e.getMessage(), "msg:")){
			message = StringUtils.replace(e.getMessage(), "msg:", "");
		}
		else{
			message = "系统出现点问题,请稍后再试!";
			e.printStackTrace(); // 输出到控制台
		}
		
		System.out.println(className + "::" + message);
        request.setAttribute("shiroLoginFailure", className);
        request.setAttribute("message", message);
		
        /** 认证失败,交给controller继续处理 */
        return true;
	}
	
}

控制器

PageController.java

package com.td.controller;

import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class PageController {

	@RequestMapping({"/index", "/login", "/"})
	public String login() {
		return "login";
	}
	
	/**
	 * 该方法会来拦截器登录认证失败后执行
	 * @return
	 */
	@RequestMapping({"/user/login"})
	public String redirectLogin() {
		/**
		 * 注意:必须使用重定向
		 * 原因:
		 * 	当前程序使用Filter进行拦截登录,如果不是重定向,那么进入服务器之后
		 * 	Filter就只会执行一次,如果要保证每次登录都经过Filter就需要使用重定向
		 * 	到登录页面
		 */
		System.out.println("登录失败,重定向到登录页面!!!");
//		return "redirect:/";
//		return "redirect:/index";
		return "redirect:/login";
	}
	
	@RequestMapping("/login_success")
	public String loginSuccess() {
		return "login_success";
	}
	
	/**
	 * shiro注解需要shiro配置在子容器才生效
	 * 1. shiro标签用于控制显示
	 * 2. @RequiresRoles用于控制有心人用url直接访问
	 * */
	@RequiresRoles("admin") 
//	@RequiresPermissions({"insert"})
	@RequestMapping("/admin")
	public String admin() {
		return "admin";
	}
	
	@ExceptionHandler({UnauthorizedException.class})
	public String noAccess() {
		return "no_access";
	}

}

View

login

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>登录</title>
</head>
<body>
	<!-- 用户登录认证 -->
	<form action="user/login" method="post" >
		用户名称:<input type="text" name="username" /><br>
		用户密码:<input type="text" name="password" /><br>
		<input type="submit" value="登录"/>
	</form>
</body>
</html>

login_success

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title></title>
</head>
<body>
	<!-- -->
	登录认证成功
    <!-- shiro标签会触发realm的受权方法,有权限才会显示 -->
    <!-- 所以只有当前用户有admin角色才会显示 -->
	<shiro:hasRole name="admin">
		<br> <a href="admin">admin页面</a>
	</shiro:hasRole>
	
		<br> <a href="user/logout">登出</a>
	
</body>
</html>

admin (只有admin角色才能访问)

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title></title>
</head>
<body>
	<!-- -->
	admin》》》》》》》》》》》》》
	
</body>
</html>

no_access (访问到权限不够的资源时会跳转到该页面)

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title></title>
</head>
<body>
	<!-- -->
	没有权限访问
</body>
</html>

运行结果

管理员用户 (tandi/tandi)

登录成功后

访问admin页面

普通用户(lisi/lisi)

登录成功后

访问admin页面

总结

1、localhost:8080 进入登录页面

2、登录表单提交数据,被UserFormAuthenticationFilter拦截,并创建一个UsernamePasswordToken对象提供给Reaml使用

3、UserAuthorizingRealm的认证方法doGetAuthenticationInfo接受到token,进行登录认证操作

认证失败:返回到UserFormAuthenticationFilter的onLoginFailure方法

认证成功:后返回UserFormAuthenticationFilter的issueSuccessRedirect方法

4、如果在用户操作系统过程中访问到了需要权限才能操作的功能(连接、方法、页面等等),就会触发UserAuthorizingRealm中的受权方法doGetAuthorizationInfo进行当前用户的受权操作

以上是关于servlet3.0全注解spring整合shiro的主要内容,如果未能解决你的问题,请参考以下文章

重新学习Spring注解——servlet3.0

23spring注解学习——servlet3.0异步和spring的异步

Spring 注解驱动WEB 注解开发

Spring注解驱动开发-AOPTx和Servlet3.0

SSM(StringMvc+Spring+MyBatis)全注解整合

SpringSpringMVCMyBatis 的全注解方式整合