spring boot + shiro实现登录功能

一、用maven导入相关依赖

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.3.RELEASE</version>
  </parent>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.2.2</version>
    </dependency>
  </dependencies>

二、先创建一个验证的MyShiroRealm类,继承AuthorizingRealm类,重写doGetAuthorizationInfo方法和doGetAuthenticationInfo方法

package springboot.shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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 java.util.HashSet;
import java.util.Set;

public class MyShiroRealm extends AuthorizingRealm {
	@Override
		protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		//获取用户
		User user = (User)SecurityUtils.getSubject().getPrincipal();
		SimpleAuthorizationInfo info =  new SimpleAuthorizationInfo();
		//获取用户角色
		Set<String> roleSet = new HashSet<String>();
		roleSet.add("100002");
		info.setRoles(roleSet);
		//获取用户权限
		Set<String> permissionSet = new HashSet<String>();
		permissionSet.add("sys:acc:aaaa");
		permissionSet.add("权限删除");
		info.setStringPermissions(permissionSet);
		return info;
	}

	/**
	 * 验证用户身份,如果验证失败,返回null或者异常(带返回message),跳转到login的post链接
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
		UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
		String username = token.getUsername();
		if(!"aaa".equals(username)){
			throw new AuthenticationException("用户不存在");
		}
		//没连接数据库,写死账号密码
		User user = new User("aaa","123");
		//进行认证,将正确数据给shiro处理
		//密码不用自己比对,AuthenticationInfo认证信息对象,一个接口,new他的实现类对象SimpleAuthenticationInfo
		AuthenticationInfo authcInfo=new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
		//清之前的授权信息
		super.clearCachedAuthorizationInfo(authcInfo.getPrincipals());
		SecurityUtils.getSubject().getSession().setAttribute("login", user);
		return authcInfo;//返回给安全管理器,securityManager,由securityManager比对数据库查询出的密码和页面提交的密码
	}
}

注:doGetAuthorizationInfo方法只有在以下三种情况才会被调用,正常登录成功不进doGetAuthorizationInfo方法

(1)subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己调用这个是否有什么角色或者是否有什么权限的时候;

(2)@RequiresRoles("admin") :在方法上加注解的时候;

(3)[@shiro.hasPermission name = "admin"][/@shiro.hasPermission]:在页面上加shiro标签的时候,即进这个页面的时候扫描到有这个标签的时候。

三、创建ShiroConfiguration类配置shiro

package springboot.shiro;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Shiro 配置
 *
 * Apache Shiro 核心通过 Filter 来实现,就像SpringMvc 通过DispachServlet 来控制一样。
 * Filter 是通过URL规则来进行过滤和权限校验,所以需要定义一系列关于URL的规则和访问权限。
 *
 */
@Configuration
public class ShiroConfiguration {
	/**
	 * ShiroFilterFactoryBean 处理拦截资源文件问题。
	 * 注:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
	 * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
	 *
	 Filter Chain定义说明
	 1、一个URL可以配置多个Filter,使用逗号分隔
	 2、当设置多个过滤器时,全部验证通过,才视为通过
	 3、部分过滤器可指定参数,如perms,roles
	 *
	 */
	@Bean
	public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

		// 必须设置 SecurityManager
		shiroFilterFactoryBean.setSecurityManager(securityManager);

		// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
		shiroFilterFactoryBean.setLoginUrl("/login");
		// 登录成功后要跳转的链接
		shiroFilterFactoryBean.setSuccessUrl("/index");
		// 未授权界面;
		shiroFilterFactoryBean.setUnauthorizedUrl("/403");

		// 拦截器.
		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
		// 配置不会被拦截的链接 顺序判断
		filterChainDefinitionMap.put("/static/**", "anon");
		filterChainDefinitionMap.put("/verify", "anon");

		// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
		filterChainDefinitionMap.put("/logout", "logout");

		filterChainDefinitionMap.put("/add", "perms[权限添加]");

		// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
		// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
		filterChainDefinitionMap.put("/**", "authc");

		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		System.out.println("Shiro拦截器工厂类注入成功");
		return shiroFilterFactoryBean;
	}

	@Bean
	public SecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		// 设置realm.
		securityManager.setRealm(myShiroRealm());
		return securityManager;
	}

	/**
	 * 身份认证realm; (这个需要自己写,账号密码校验;权限等)
	 *
	 */
	@Bean
	public MyShiroRealm myShiroRealm() {
		return new MyShiroRealm();
	}
}

注:这里我登录页面访问的是login,form表单提交的是verify,所以需要打开verify的访问权限

@RequestMapping("/login")
public String login() {
    return "login";
}
<form action="verify" method="post">
filterChainDefinitionMap.put("/verify", "anon");

四、controller代码

package springboot.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.context.annotation.Bean;
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;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import springboot.shiro.User;

import javax.servlet.http.HttpServletRequest;

@Controller
public class JspController {
    @RequestMapping("/verify")
    public String userlogin(User user, RedirectAttributes attr, Model model) {
        if(user.isEmpty()){return "login";}
        String username=user.getUsername();
        String password=user.getPassword();
        UsernamePasswordToken token = new UsernamePasswordToken(username,password,false);
        Subject currentUser = SecurityUtils.getSubject();
        try {
            //调用realm的认证方法
            currentUser.login(token);
        } catch(IncorrectCredentialsException e){
            model.addAttribute("message", "密码错误");
            return "login";
        } catch (AuthenticationException e) {
            attr.addFlashAttribute("test", "登录失败");
            return"redirect:/login?message='登录失败'";
        }
        return "redirect:/index";
    }

    @RequestMapping(value="logout",method =RequestMethod.GET)
    public String logout(HttpServletRequest request){

        //subject的实现类DelegatingSubject的logout方法,将本subject对象的session清空了
        //即使session托管给了redis ,redis有很多个浏览器的session
        //只要调用退出方法,此subject的、此浏览器的session就没了
        try {
            //退出
            SecurityUtils.getSubject().logout();

        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
        return "login";

    }

    @RequestMapping(value="403")
    public String unAuth(){

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

    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

}

注:

(1)登录调用了logout方法,清空了subject对象的session

SecurityUtils.getSubject().logout();

(2)如果使用重定向的话,可以使用RedirectAttributes对象传参数,Jsp页面接收值比较特殊

attr.addFlashAttribute("test", "登录失败");
return"redirect:/login?message='登录失败'";
${sessionScope['org.springframework.web.servlet.support.SessionFlashMapManager.FLASH_MAPS'][0]['test']

(3)转发使用Model传参

model.addAttribute("message", "密码错误");
return "login";
${message}

五、JSP页面代码

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script type="text/javascript" src="js/layer/layer.js"></script>
</head>
<body style="text-align: center">
    <form action="verify" method="post">
        <p>账号:<input type="text" name="username" value="admin"/></p>
        <p>密码:<input type="text" name="password" value="123456"/></p>
        <P><input type="checkbox" name="rememberMe" />记住我</P>
        <p><input type="submit" value="登录"/></p>
        <br/>
        <div>${sessionScope['org.springframework.web.servlet.support.SessionFlashMapManager.FLASH_MAPS'][0]['test']}${message}</div>
    </form>
</body>
<script type="text/javascript">

</script>
</html>

猜你喜欢

转载自blog.csdn.net/qq_15260315/article/details/81164100