准备工作
- 一张用户表,需要字段:用户名、密码、权限
Shiro的配置类
ShiroConfig.java
package com.rain.demo.config;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
// 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/index", "anon");
filterChainDefinitionMap.put("/login", "anon");
// 设置授权过滤器
filterChainDefinitionMap.put("/add", "perms[1]");
/* 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 */
// authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/**", "authc");
// 拦截后进入的url
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
// shiroFilterFactoryBean.setLoginUrl("/noLogin");
// 登录成功后要跳转的链接
// shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean(name = "shiroRealm")
public ShiroRealm shiroRealm() {
return new ShiroRealm();
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager(ShiroRealm shiroRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager());
securityManager.setRealm(shiroRealm);
securityManager.setRememberMeManager(rememberMeManager());
securityManager.setCacheManager(cacheManager());
return securityManager;
}
@Bean
public EhCacheManager cacheManager() {
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return ehCacheManager;
}
/**
* shiro session的管理
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(sessionDAO());
sessionManager.setGlobalSessionTimeout(2 * 60 * 60 * 1000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionIdCookieEnabled(true);
SimpleCookie sessionIdCookie = new SimpleCookie("DEFAULT_SESSIONID");
sessionIdCookie.setMaxAge(2 * 60 * 60);
sessionManager.setSessionIdCookie(sessionIdCookie);
return sessionManager;
}
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
SimpleCookie rememberMeCookie = new SimpleCookie("RememberMe");
rememberMeCookie.setMaxAge(7 * 24 * 60 * 60);
rememberMeManager.setCookie(rememberMeCookie);
return rememberMeManager;
}
@Bean
public EnterpriseCacheSessionDAO sessionDAO() {
EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();
sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
sessionDAO.setSessionIdGenerator(sessionIdGenerator());
return sessionDAO;
}
@Bean
public JavaUuidSessionIdGenerator sessionIdGenerator() {
return new JavaUuidSessionIdGenerator();
}
@Bean
public ShiroLoginFilter shiroLoginFilter(){
return new ShiroLoginFilter();
}
}
Realm类
ShiroRealm.java
package com.rain.demo.config;
import com.rain.demo.entity.User;
import com.rain.demo.service.UserService;
import org.apache.shiro.SecurityUtils;
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.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.apache.shiro.subject.Subject;
import javax.annotation.Resource;
public class ShiroRealm extends AuthorizingRealm {
@Resource
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行授权逻辑");
// 给资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 添加资源的授权字符串
info.addStringPermission("user:add"); // 直接添加授权字符串
// 从数据库里取授权权限
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
info.addStringPermission(user.getRole().toString());
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行认证逻辑");
UsernamePasswordToken user = (UsernamePasswordToken) token;
User currentUser = userService.getUser(user.getUsername());
// 判断用户名
if (currentUser == null) return null;
// 判断密码
else return new SimpleAuthenticationInfo(currentUser, currentUser.getPassword(), "");
}
}
Controller类
Start.java
package com.rain.demo.controller;
import com.rain.demo.entity.Result;
import com.rain.demo.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/")
public class Start {
@RequestMapping("login")
@ResponseBody
public Result login(User user){
Result result = new Result();
result.setMessage("用户名不能为空");
if (null == user.getUsername())
return result;
/* 编写用户认证操作 */
// 1、获取Subject
Subject subject = SecurityUtils.getSubject();
// 2、封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
try {
// 3、执行登录方法
subject.login(token);
} catch (UnknownAccountException uae) {
result.setMessage("未知账户");
} catch (IncorrectCredentialsException ice) {
result.setMessage("密码不正确");
} catch (LockedAccountException lae) {
result.setMessage("账户已锁定");
} catch (ExcessiveAttemptsException eae) {
result.setMessage("用户名或密码错误次数过多");
} catch (AuthenticationException ae) {
result.setMessage("用户名或密码不正确");
}
// 验证是否登录成功
if (subject.isAuthenticated()) {
result.setMessage("登录成功");
System.out.println("登录成功");
} else {
token.clear();
System.out.println("重新登录");
}
return result;
}
@RequestMapping("add")
public String add(){
return "add";
}
@RequestMapping("403")
public String noAuth(){
return "403";
}
@RequestMapping("update")
public String update(){
return "update";
}
@RequestMapping("errors")
@ResponseBody
public String error(){
return "被拦截了";
}
@RequestMapping("index")
public String index(){
return "index";
}
}
一个测试页面
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="../static/jquery-3.3.1.min.js"></script>
<title>Login</title>
</head>
<body>
这是登录页面 <br>
<a href="/add">add</a><br>
<a href="/update">update</a><br>
<a href="/logout">logout</a><br>
<button id="btn">点击登录</button>
<script>
$(function () {
$("#btn").on("click",function () {
$.ajax({
type:'get',
url:'http://localhost:8089/login',
dataType:'json',
data:{
username : "123456",
password : "123"
},
success: function(res) {
console.log(res);
$("#msg").html(res.message);
//请求成功
},
error: function(err) {
console.log("error");
}
});
});
})
</script>
<div id="msg"></div>
</body>
</html>
一个避免Springboot拦截静态资源的配置类
SpringConfig.java
package com.rain.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class SpringConfig {
@Configuration
public class MyMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
super.addResourceHandlers(registry);
}
}
}
让页面跳转变成json
如果我们发送Ajax请求,shiro拦截后不是返回json数据,而是跳转到另一个页面,为了解决这个问题,需要我们重新写一个过滤器
ShiroLoginFilter.java
package com.rain.demo.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rain.demo.entity.Result;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ShiroLoginFilter extends FormAuthenticationFilter {
/**
* 在访问controller前判断是否登录,返回json,不进行重定向。
* @param request
* @param response
* @return true-继续往下执行,false-该filter过滤器已经处理,不继续执行其他过滤器
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
ObjectMapper mapper = new ObjectMapper();
if (isAjax(request)) {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
Result resultData = new Result();
resultData.setMessage("登录认证失效,请重新登录!");
httpServletResponse.getWriter().write(mapper.writeValueAsString(resultData));
} else {
// saveRequestAndRedirectToLogin(request, response);
// 非ajax请求重定向为登录页面
httpServletResponse.sendRedirect("/login");
}
return false;
}
private boolean isAjax(ServletRequest request){
String header = ((HttpServletRequest) request).getHeader("X-Requested-With");
if("XMLHttpRequest".equalsIgnoreCase(header)){
return Boolean.TRUE;
}
return Boolean.FALSE;
}
}
然后在ShiroConfig中加入
@Bean
public ShiroLoginFilter shiroLoginFilter(){
return new ShiroLoginFilter();
}
可能用到的一些实体类
Result.java
package com.rain.demo.entity;
public class Result {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
User.java
package com.rain.demo.entity;
public class User {
private Integer userId;
private String username;
private String password;
private Integer role;
private String isDelete;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username == null ? null : username.trim();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password == null ? null : password.trim();
}
public Integer getRole() {
return role;
}
public void setRole(Integer role) {
this.role = role;
}
public String getIsDelete() {
return isDelete;
}
public void setIsDelete(String isDelete) {
this.isDelete = isDelete == null ? null : isDelete.trim();
}
}