servlet3.0全注解spring整合shiro

时间:2023-01-26 08:57:17

基本说明

基于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)

登录成功后

servlet3.0全注解spring整合shiro

访问admin页面

servlet3.0全注解spring整合shiro

普通用户(lisi/lisi)

登录成功后

servlet3.0全注解spring整合shiro

访问admin页面

servlet3.0全注解spring整合shiro

总结

1、localhost:8080 进入登录页面

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

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

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

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

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