shiro入门demo(三)认证+授权+拦截
一、session管理会话:
1、pom:
<groupId>com.demo.shiro</groupId>
<artifactId>shiro-filter-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>
<!-- AOP依赖,必须,否则shiro权限拦截验证不生效 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
2、Realm:这里没有连接数据库,在UserConstants中假设数据
/**
* 模拟数据库查询数据,假设有:用户名/密码/角色/资源
* admin/123/xtgly/user_manage、role_manage、menu_manage、school_manage
* zs/123/userAdmin、roleAdmin/user_manage、role_manage、menu_manage
* ls/123/schoolAdmin/school_manage
*/
public class UserConstants {
public static Map<String, String> getUsers() {
Map<String,String> users = new HashMap<>();
users.put("admin","123");
users.put("zs","123");
users.put("ls","123");
return users;
}
public static Map<String,List<String>> getUserRoles() {
Map<String,List<String>> userRoles = new HashMap<>();
//admin
List<String> adminRoles = new ArrayList<>();
adminRoles.add("xtgly");
userRoles.put("admin",adminRoles);
//zs
List<String> zsRoles = new ArrayList<>();
zsRoles.add("userAdmin");
zsRoles.add("roleAdmin");
userRoles.put("zs",zsRoles);
//ls
List<String> lsRoles = new ArrayList<>();
lsRoles.add("schoolAdmin");
userRoles.put("ls",lsRoles);
return userRoles;
}
public static Map<String,List<String>> getUserPermissions() {
Map<String,List<String>> userPermissions = new HashMap<>();
List<String> lsPermissions = new ArrayList<>();
//ls
lsPermissions.add("school_manage");
userPermissions.put("ls",lsPermissions);
//zs
List<String> zsPermissions = new ArrayList<>();
zsPermissions.add("user_manage");
zsPermissions.add("role_manage");
zsPermissions.add("menu_manage");
userPermissions.put("zs",zsPermissions);
//admin
List<String> adminPermissions = new ArrayList<>();
adminPermissions.add("school_manage");
userPermissions.put("admin",adminPermissions);
return userPermissions;
}
}
public class MyRolePermissionRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
String primaryPrincipal = (String) principal.getPrimaryPrincipal();
Map<String, List<String>> userRoles = UserConstants.getUserRoles();
List<String> roles = userRoles.get(primaryPrincipal);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRoles(roles);
//资源
Map<String, List<String>> userPermissions = UserConstants.getUserPermissions();
List<String> permissions = userPermissions.get(primaryPrincipal);
simpleAuthorizationInfo.addStringPermissions(permissions);
return simpleAuthorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
Map<String,String> dbUsers = UserConstants.getUsers();
Set<String> dbUserNames = dbUsers.keySet();
//验证用户名
if(!dbUserNames.contains(username)){
throw new UnknownAccountException();
}
//验证密码
String dbPwd = dbUsers.get(username);
if(!dbPwd.equals(password)){
throw new IncorrectCredentialsException();
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,password,this.getName());
return simpleAuthenticationInfo;
}
}
3、拦截器:
public class ShiroFormAuthenticationFilter extends FormAuthenticationFilter{
Logger logger = LoggerFactory.getLogger(ShiroFormAuthenticationFilter.class);
/**
* 未登陆
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (!isLoginRequest(request, response)) {
this.handleErrorMsg(request,response);
return false;
}
if (isLoginSubmission(request, response)) {
if (logger.isTraceEnabled()) {
logger.trace("Login submission detected. Attempting to execute login.");
}
return executeLogin(request, response);
} else {
if (logger.isTraceEnabled()) {
logger.trace("Login page view.");
}
return true;
}
}
private void handleErrorMsg(ServletRequest request, ServletResponse response) throws IOException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
resp.setHeader("Access-Control-Allow-Credentials", "true");
resp.setContentType("application/text; charset=utf-8");
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter();
out.println("登陆失效");
out.flush();
out.close();
}
}
4、配置文件:
import com.demo.shiro.filter.ShiroFormAuthenticationFilter;
import com.demo.shiro.realm.MyRolePermissionRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//域
@Bean
public Realm getMyShiroRealm(){
return new MyRolePermissionRealm();
}
//入口
@Bean
public SecurityManager getMySecurityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(this.getMyShiroRealm());
SecurityUtils.setSecurityManager(securityManager);
return securityManager;
}
//拦截器配置
@Bean
public ShiroFilterFactoryBean shirFilter() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(this.getMySecurityManager());
//shiroFilterFactoryBean.setLoginUrl("/login");
/**
* 只写这个,访问报错
* {
* "timestamp": 1703231180623,
* "status": 404,
* "error": "Not Found",
* "message": "No message available",
* "path": "/shiroTest/login.jsp"
* }
*/
Map<String, String> filterChainDefinitionMap = new HashMap<>();
filterChainDefinitionMap.put("/**","authc");
filterChainDefinitionMap.put("/login/login","anon");
LinkedHashMap<String, Filter> filtsMap=new LinkedHashMap<String, Filter>();
filtsMap.put("authc",new ShiroFormAuthenticationFilter() );
shiroFilterFactoryBean.setFilters(filtsMap);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//以下为注解支持配置
/**
* Shiro生命周期处理器
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 开启Shiro-aop注解支持
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
5、util
public class SessionUtil {
public static String getUser() {
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
return (String) session.getAttribute("currUserAccount");
}
}
6、controller:
@RequestMapping("/login")
@RestController
public class LoginController {
@RequestMapping("/login")
public String login(String username,String password){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(
username, password
);
try {
subject.login(token);
System.out.println("认证成功");
Session session = subject.getSession();
//缓存用户
session.setAttribute("currUserAccount",username);
return (String) session.getId();
} catch (AuthenticationException e) {
System.out.println("认证失败");
e.printStackTrace();
}
return null;
}
@RequestMapping("/logOut")
public void logOut(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
}
}
@RequestMapping("/user")
@RestController
public class UserManageController {
@RequestMapping("/test")
@RequiresRoles("userAdmin")
public String test(){
return "这是用户管理";
}
@RequestMapping("/getUser")
public String getUser(){
return SessionUtil.getUser();
}
}
@RequestMapping("/school")
@RestController
public class SchoolManageController {
@RequestMapping("/test")
@RequiresPermissions("school_manage")
public String test(){
return "这是学校管理";
}
}
测试:
(1)admin用户
访问无角色权限的接口、有资源权限校验的接口
? ?
(2)zs用户
? ? ? ?
访问无资源权限接口、有角色接口
退出登陆,并再次访问接口
? ??
二、token令牌校验:
以上使用了session存储当前会话的用户,前端不需要带入用户身份信息;后端自然也无需校验用户是否合法,只需要(shiro自动)判断是否登陆了即可,当前浏览器session会话有效时间内,对后端都是同一个用户。关于token和session的对比在另一篇博客有讲解。项目中一般使用token管理会话。
1)自定义token:如UUID,登陆成功后生成一个UUID作为token,同时将userId与token键值对缓存到redis中并设置失效时间,再自定义拦截器根据前端传入的token从redis中取userId,如果userId为空则不通过。这种方法对于token是错误的还是失效了无法区分。
2)JWT:更安全的token。根据username、password生成token,可以根据token反解析出userId,由此判断token是否正确;缓存到redis中设置过期时间,可以判断是否过期。
下面以jwt为例,管理用户身份。代码修改点:
1、pom新增依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
2、新增redis配置:application新增redis连接配置
my.redis.server.host=127.0.0.1
my.redis.server.port=6379
my.redis.server.password = wtyy
my.redis.server.jedis.pool.maxTotal=500
my.redis.server.jedis.pool.maxIdle=10
my.redis.server.jedis.pool.maxWaitMillis=5000
my.redis.server.jedis.pool.min-idle=5
my.redis.server.timeout=5000
?并新增redis配置文件JedisConfig和redis操作类RedisClient
3、拦截器部分:
(1)shiro配置类中:设置/login/logOut接口白名单(/login/login前面已经设置过了),其他接口均经过自定义JwtFilter拦截器:
Map<String, String> filterChainDefinitionMap = new HashMap<>();
filterChainDefinitionMap.put("/**","authc");
filterChainDefinitionMap.put("/login/login","anon");
filterChainDefinitionMap.put("/login/logOut","anon");
LinkedHashMap<String, Filter> filtsMap=new LinkedHashMap<String, Filter>();
//filtsMap.put("authc",new ShiroFormAuthenticationFilter() );
//非登陆接口,需要校验token是否合法
filtsMap.put("authc",new JwtFilter() );
shiroFilterFactoryBean.setFilters(filtsMap);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
(2)JwtFilter拦截器:校验token正确性
public class JwtFilter extends BasicHttpAuthenticationFilter {
private static RedisClient redisClient;
/**
*
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
System.out.println("JwtFilter.isAccessAllowed开始");
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader("token");
if (StringUtils.isEmpty(token)) {
System.out.println("JwtFilter.isAccessAllowed token为空");
this.hanldeErrorResp(response,"token为空");
return false;
}
String jwtUserId = JwtUtil.getUsername(token);
if(StringUtils.isEmpty(jwtUserId)){
System.out.println("JwtFilter.isAccessAllowed token不正确");
this.hanldeErrorResp(response,"token不正确");
return false;
}
ServletContext servletContext = request.getServletContext();
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
redisClient = (RedisClient) context.getBean("redisClient");
String redisToken = redisClient.get(jwtUserId);
if(StringUtils.isEmpty(redisToken)){
System.out.println("JwtFilter.isAccessAllowed token过期");
this.hanldeErrorResp(response,"token过期");
return false;
}
if(token.equals(redisToken)){
return true;
}
this.hanldeErrorResp(response,"token过期");
return false;
}
private void hanldeErrorResp(ServletResponse response, String errorMsg) {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/text;charset=utf-8");
PrintWriter out = null;
try {
out = response.getWriter();
} catch (IOException e) {
e.printStackTrace();
}
out.write(errorMsg);
}
}
4、新增jwt的util,用于生成jwt和获取userId:
package com.demo.shiro.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.demo.shiro.constant.UserConstants;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
public class JwtUtil {
/**
* 校验token是否正确
* @param token 密钥
* @param secret 用户的密码
* @return 是否正确
*/
public static boolean verify(String token, String username, String secret) {
try {
// 根据密码生成JWT效验器
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
// 效验TOKEN
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 获得token中的信息无需secret解密也能获得
* @return token中包含的用户名
*/
public static String getUsername( HttpServletRequest req) {
try {
String token = req.getHeader("token");
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
public static String getUsername( String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 生成签名,5min(分钟)后过期
* @param username 用户名
* @param secret 用户的密码
* @return 加密的token
*/
public static String sign(String username, String secret) {
Date date = new Date(System.currentTimeMillis() + UserConstants.EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带username信息
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
}
}
?5、controller接口修改:
@RequestMapping("/login")
@RestController
public class LoginController {
@Autowired
private RedisClient redisClient;
@RequestMapping("/login")
public String login(String username,String password){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(
username, password
);
try {
subject.login(token);
System.out.println("认证成功");
String jwtToken = JwtUtil.sign(username, password);
redisClient.set(username,jwtToken);
return jwtToken;
} catch (AuthenticationException e) {
System.out.println("认证失败");
e.printStackTrace();
}
return null;
}
@RequestMapping("/logOut")
public void logOut(){
Subject subject = SecurityUtils.getSubject();
String username = (String) subject.getPrincipal();
redisClient.delete(username);
subject.logout();
}
}
/user/getUser接口修改,(当然session管理也可以用下面这种方法获取当前登陆用户)
@RequestMapping("/getUser")
public String getUser(){
Subject subject = SecurityUtils.getSubject();
return (String) subject.getPrincipal();
}
测试:
1)登陆
2)访问业务接口:
? ??
3)退出登陆,使用原来的token再次访问业务接口:
? ??
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!