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再次访问业务接口:

    

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w_t_y_y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值