Shiro Web样例
以下内容仅为演示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