您的当前位置:首页使用shiro结合spring框架进行用户认证

使用shiro结合spring框架进行用户认证

2024-12-12 来源:哗拓教育

Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。

项目具体实现层次结构如下:


HP-shiro-spring-test.png
  1. 首先定义shiro.ini,用来指定用户身份和凭据。
[users]
root = secret, root
guest = guest, guest
gandhi = 12345, role1, role2
bose = 67890, role2

[roles]
root = *
role1 = filesystem:*,system:*
role2 = "calculator:add,subtract"

上面的shiro.ini文件定义了四个用户,格式为“用户名=密码,角色”;每个角色拥有一些权限。
root拥有所有的权限,role1拥有filesystem以及system的所有权限,role2拥有calculator的add和substract权限。这些权限在当前用户对系统资源进行访问的时候要用到。

2.定义配置文件
web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app  
    
          
    version="2.5">
    <!-- 作用:在启动Web容器时,自动装配Spring applicationContext.xml的配置信息 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    
    
    <listener>
        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
    </listener>    

    <!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <!-- Make sure any request you want accessible to Shiro is filtered. catches all 
        requests. Usually this filter mapping is defined first (before all others) to 
        ensure that Shiro works in subsequent filters in the filter chain: -->
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher> 
        <dispatcher>FORWARD</dispatcher> 
        <dispatcher>INCLUDE</dispatcher> 
        <dispatcher>ERROR</dispatcher>        
    </filter-mapping>
    
    
    <servlet>
        <servlet-name>springMvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    
    <!-- 
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>com.hp.shiro.simplerbac.controller.LoginServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>logout</servlet-name>
        <servlet-class>com.hp.shiro.simplerbac.controller.LogoutServlet</servlet-class>
    </servlet>
    
    <servlet>
        <servlet-name>home</servlet-name>
        <servlet-class>com.hp.shiro.simplerbac.controller.HomeServlet</servlet-class>
    </servlet>
     -->
    <servlet-mapping>
        <servlet-name>springMvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
<!-- 
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>logout</servlet-name>
        <url-pattern>/logout</url-pattern>
    </servlet-mapping>
    
    <servlet-mapping>
        <servlet-name>home</servlet-name>
        <url-pattern>/home/*</url-pattern>
    </servlet-mapping>
 -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

springMvc-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans 
    
    
    
     
         
         
    
    <!-- is actually rather pointless. It declares explicit support 
    for annotation-driven MVC controllers (i.e.@RequestMapping, 
    @Controller, etc), even though support for those is the default
     behaviour  
     当我们需要controller返回一个map的json对象时,可以设定<mvc:annotation-driven />
     会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter
      两个bean,是spring MVC为@Controllers分发请求所必须的。并提供了:数据绑定支持,
    @NumberFormatannotation支持,@DateTimeFormat支持,@Valid支持,读写XML
    的支持(JAXB),读写JSON的支持(Jackson)-->
    <mvc:annotation-driven />
    
    <!-- 指定静态资源的位置,例如js,css和图片等文件,放到webroot文件夹下 -->
    <mvc:resources mapping="/css/**" location="/css/" />
    <mvc:default-servlet-handler />
    
    <!-- 启用spring mvc注解  例如 @Required, @Autowired, @PostConstruct-->
    <context:annotation-config />
    
    <!-- 设置使用注解的类所在的包名 -->
    <context:component-scan base-package="com.hp.shiro.simplerbac.controller" />
    
    <!--完成请求和注解pojo的映射。 
    当我们需要controller返回一个map的json对象时,可以设定<mvc:annotation-driven />,
    同时设定<mvc:message-converters> 标签,设定字符集和json处理类 -->
    <bean
        class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean
                    class="org.springframework.http.converter.StringHttpMessageConverter">
                    <property name="supportedMediaTypes">
                        <list>
                            <value>text/plain;charset=UTF-8</value>
                        </list>
                    </property>
                </bean>
            </list>
        </property>
    </bean>
    
    
    <!-- <bean
        class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" />
         -->
         
    <!-- 视图解析器,对转向页面的路径解析。prefix:前缀,suffix:后缀 -->
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
    
    <!-- <bean id="multipartResolver"  
           -->

</beans>

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans 
     
    
    
     
     
     
    
    <bean id="iniRealm" class="org.apache.shiro.realm.text.IniRealm">
        <property name="resourcePath" value="classpath:/shiro.ini" />
    </bean>

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="iniRealm" />
    </bean>
    <!--Shiro 生命周期处理器--> 
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" />
    
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>
    
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/login" />
        <property name="successUrl" value="/home/" />

        <property name="filterChainDefinitions">
            <value>
                /home/** = authc
            </value>
        </property>
    </bean>


</beans>

3.然后定义ProtectedService.java来实现功能。

package com.hp.shiro.simplerbac.bean;

import java.io.File;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.shiro.authz.annotation.RequiresPermissions;

public class ProtectedService {
    private static final List<String> USERS = Arrays.asList("root","guest","gandhi","bose");
    
    private static final List<String> ROLES = Arrays.asList("root","guest","role1","role2");
    
    @RequiresPermissions("user-roles:read")
    public List<String> getUsers() {
        return USERS;
    }
    
    @RequiresPermissions("user-roles:read")
    public List<String> getRoles() {
        return ROLES;
    }
    
    @RequiresPermissions("system:read:time")
    public Date getSystemTime() {
        return Calendar.getInstance().getTime();
    }
    
    @RequiresPermissions("calculator:add")
    public int sum(int a, int b) {
        return a+b;
    }
    
    @RequiresPermissions("calculator:subtract")
    public int diff(int a, int b) {
        return a-b;
    }
    
    @RequiresPermissions("filesystem:read:home")
    public List<String> getHomeFiles() {
        File homeDir = new File(System.getProperty("user.home"));
        return Arrays.asList(homeDir.list());
    }

    public String getGreetingMessage(String name) {
        return String.format("Hello %s",name);
    }
}

使用了@RequiresPermissions()注解来表示每一个方法的需要的permission,没有该注解的getGreetingMessage(String name)方法不要求任何权限。

4.定义两个Controller,分别是登陆/登出页面的controller和成功登陆以后完成访问系统资源功能的controller。

package com.hp.shiro.simplerbac.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;


@Controller
public class LoginController {
    @RequestMapping(value="login", method=RequestMethod.GET)
    public String login(HttpServletRequest req){
        if (SecurityUtils.getSubject().isAuthenticated()) {
            return "redirect:/home";
        } else {
            return "login";
        }
    }

    @RequestMapping(value="login", method=RequestMethod.POST)
    public String login(HttpServletRequest req,RedirectAttributes redirectAttributes,Model model) {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("access to login");
        System.out.println(username+","+password);
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        String errorMessage = null;
        try {
            SecurityUtils.getSubject().login(token);
        } catch (AuthenticationException e) {
            errorMessage = "user name doesn't exist or wrong password";
        }
        if(null == errorMessage) {
            redirectAttributes.addAttribute("username", username);
            return "redirect:/home";
        } else {
            System.out.println(errorMessage);
            req.setAttribute("errorMessage",errorMessage);
            return "login";
        }
    }
    
    @RequestMapping(value="logout")
    public String logout(HttpServletRequest req){
        SecurityUtils.getSubject().logout();
        return "redirect:/login";
    }
    
    
}

package com.hp.shiro.simplerbac.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.SecurityUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.hp.shiro.simplerbac.bean.ProtectedService;

@Controller
public class HomeController {
    @RequestMapping(value="home")
    public String home(HttpServletRequest req, HttpServletResponse response, Model model){
//      System.out.println("access to home controller.");
        String username = (String)SecurityUtils.getSubject().getPrincipal();
//      System.out.println("username:"+username);
        model.addAttribute("username", username);
        String method = req.getParameter("method");
//      System.out.println("method:"+method);
        /*
         * method可能的值value包括:
         *  <input type="hidden" name="method" value="getUsers"/>
         *  <input type="hidden" name="method" value="getRoles"/>
         *  <input type="hidden" name="method" value="getSystemTime"/>
         *  <input type="hidden" name="method" value="sum"/>
         *  <input type="hidden" name="method" value="diff"/>
         *  <input type="hidden" name="method" value="getHomeFiles"/>
         *  <input type="hidden" name="method" value="getGreetingMessage"/>
         */

        ProtectedService protectedService = new ProtectedService();
        
        try {
            if ("getUsers".equals(method)) {
                model.addAttribute("users", protectedService.getUsers());
            } else if ("getRoles".equals(method)) {
                model.addAttribute("roles", protectedService.getRoles());
            } else if ("getSystemTime".equals(method)) {
                model.addAttribute("systemTime", protectedService.getSystemTime());
            } else if ("sum".equals(method)) {
                int a = Integer.parseInt(req.getParameter("a"));
                int b = Integer.parseInt(req.getParameter("b"));
                model.addAttribute("sum",protectedService.sum(a, b));
            } else if ("diff".equals(method)) {
                int a = Integer.parseInt(req.getParameter("a"));
                int b = Integer.parseInt(req.getParameter("b"));
                model.addAttribute("diff",protectedService.diff(a, b));
            } else if ("getHomeFiles".equals(method)) {
                model.addAttribute("homeFiles",protectedService.getHomeFiles());
            } else if ("getGreetingMessage".equals(method)) {
                String name = req.getParameter("name");
                model.addAttribute("greetingMessage",protectedService.getGreetingMessage(name));
            }
        } catch(Exception e) {
            model.addAttribute("errorMessage", e.getMessage());
        }
        
        return "home";
    }
}

显示全文