基本说明
基于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进行当前用户的受权操作