以下内容仅为演示Web应用中shiro的基本功能使用。

一、工程代码

1、pom.xml

添加spring boot与shiro的依赖:

<!-- Inherit defaults from Spring Boot -->
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.2.2.RELEASE</version>
</parent>

<dependencies>
	<dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-spring-boot-web-starter</artifactId>
		<version>1.6.0</version>
	</dependency>
</dependencies>

2、application.properties

配置静态资源目录与登录页面:

spring.resources.static-locations=file:G:/demo

shiro.loginUrl = /login.html

3、自定义Realm

自定义身份验证与授权:

import java.util.ArrayList;
import java.util.List;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class CustomRealm extends AuthorizingRealm {

	/**
	 * 授权
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		String userName = (String) principals.getPrimaryPrincipal();
		List<String> permissionList = new ArrayList<String>();
		permissionList.add("user:query");
		if (userName.equals("wzk")) {
			permissionList.add("user:run");
		}
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		info.addStringPermissions(permissionList);
		info.addRole("admin");
		return info;
	}

	/**
	 * 身份验证
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		String userName = (String) token.getPrincipal();
		if ("".equals(userName)) {
			return null;
		}
		SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userName, token.getCredentials(), this.getName());
		return info;
	}

}

4、Shiro配置

配置Realm和请求过滤链(从上到下依次执行):

import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {

	@Bean
	public Realm customRealm() {
		return new CustomRealm();
	}
	
	@Bean
	public ShiroFilterChainDefinition shiroFilterChainDefinition() {
		DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
		chainDefinition.addPathDefinition("/js/**", "anon");
		chainDefinition.addPathDefinition("/test", "anon"); 
		chainDefinition.addPathDefinition("/custom/login", "anon"); 
		chainDefinition.addPathDefinition("/login.html", "anon"); 
		chainDefinition.addPathDefinition("/**", "authc"); 
		return chainDefinition;
	}	
}

5、自定义Controller

  • MyController

此控制器中的test请求在上面定义为anon,即匿名访问。

@RestController
public class MyController {

	@RequestMapping(value="/test")
	public String test() {
		return "test";
	}
}
  • CustomController

此控制器中的请求除login外,其余均需身份验证(authc)。

import java.util.Random;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/custom")
@RestController
public class CustomController {

	@RequestMapping(value="/login")
	public String login(@RequestParam("username")String username, @RequestParam("password")String password) {
		Subject subject = SecurityUtils.getSubject();
		UsernamePasswordToken token = new UsernamePasswordToken(username, password);
		try {
			subject.login(token);
			subject.getSession().setAttribute("userId", new Random().nextInt(1000));
		} catch (UnknownAccountException e) {
			e.printStackTrace();
			return "failed";
		} catch (IncorrectCredentialsException e) {
			e.printStackTrace();
			return "failed";
		}
		return "success";
	}
	
	@RequestMapping(value="/logout")
	public String logout() {
		Subject subject = SecurityUtils.getSubject();
		subject.logout();
		return "bye";
	}
	
	@RequiresPermissions("user:run")
	@RequestMapping(value="/auth")
	public String auth() {
		return "auth ok";
	}
	
	@RequiresPermissions("user:query")
	@RequestMapping(value="/query")
	public String query() {
		Subject subject = SecurityUtils.getSubject();
		Object userId = subject.getSession().getAttribute("userId");
		return String.format("userId: %s", userId);
	}
	
	@RequestMapping(value="/hello")
	public String hello() {
		Subject subject = SecurityUtils.getSubject();
		return String.format("Hi, %s. Authenticated: %s, Admin: %s", subject.getPrincipal(), subject.isAuthenticated(), subject.hasRole("admin"));
	}
}

6、ExceptionResolver

配置异常处理类:

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

import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

public class MyExceptionResolver implements HandlerExceptionResolver{

	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
			Exception ex) {
		if (ex instanceof AuthorizationException) {
			ModelAndView mv = new ModelAndView("/401.html");
			return mv;
		}
		return null;
	}

}

7、HTML

由于仅演示使用,页面只是简单的显示提示信息:

  • login.html
<body>
	<h3>请先登录!</h3>
</body>
  • 401.html
<html>
	<head>
		<title>401</title>
	</head>
	<body>
		<h3>没有访问权限</h3>
	</body>
</html>

8、启动程序

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@SpringBootApplication
public class App 
{
	public static void main( String[] args )
	{
		SpringApplication.run(App.class, args);
	}
	
	@Bean
	public MyExceptionResolver myExceptionResolver() {
		return new MyExceptionResolver();
	}
	
}

二、测试

1、未授权

  • /test

未登录时访问http://localhost:8080/test/,访问成功,页面显示:

test
  • /custom/query

未登录时访问http://localhost:8080/custom/query,访问失败,页面跳转到http://localhost:8080/login.html,显示:

请先登录!

2、登录

访问http://localhost:8080/custom/login?username=wzk&password=123登录,此时会调用CustomRealm.doGetAuthenticationInfo()方法做身份验证,页面显示:

success

3、访问授权资源

登录之后访问需要授权的资源时都会调用CustomRealm.doGetAuthorizationInfo()方法。

此时访问http://localhost:8080/custom/query,访问成功,页面显示:

userId: 546

访问http://localhost:8080/custom/hello,访问成功,页面显示:

Hi, wzk. Authenticated: true, Admin: true

访问http://localhost:8080/custom/auth,访问成功,页面显示:

auth ok

如果登录时使用的username不是wzk,例如:http://localhost:8080/custom/login?username=albert&password=123,此时访问http://localhost:8080/custom/auth时会抛出授权异常AuthorizationException,因为没有user:run的权限。页面显示如下:

没有访问权限

4、登出

访问http://localhost:8080/custom/logout即可退出登录,页面显示:

bye
相关链接

Shiro简介