栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

shiro(session+redis),基于springboot,基于前后端分离,从登录认证到鉴权

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

shiro(session+redis),基于springboot,基于前后端分离,从登录认证到鉴权

这个demo是基于springboot项目的。

名词介绍:
Shiro
主要分为 安全认证 和 接口授权 两个部分,其中的核心组件为 Subject、 SecurityManager、 Realms,公共部分 Shiro 都已经为我们封装好了,我们只需要按照一定的规则去编写响应的代码即可…
Subject
表示主体,将用户的概念理解为当前操作的主体,因为它即可以是一个通过浏览器请求的用户,也可能是一个运行的程序,外部应用与 Subject 进行交互,记录当前操作用户。Subject 代表了当前用户的安全操作
SecurityManager
则管理所有用户的安全操作。
SecurityManager
即安全管理器,对所有的 Subject 进行安全管理,并通过它来提供安全管理的各种服务(认证、授权等)
Realm
充当了应用与数据安全间的 桥梁 或 连接器。当对用户执行认证(登录)和授权(访问控制)验证时,Shiro 会从应用配置的 Realm 中查找用户及其权限信息。

项目结构

1.导入shiro依赖
        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            junit
            junit
            4.12
            test
        

        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.projectlombok
            lombok
            true
        

        
            mysql
            mysql-connector-java
            runtime
        

        
            org.springframework.boot
            spring-boot-starter-jdbc
        

        
            com.baomidou
            mybatis-plus-boot-starter
            3.4.2
        
        
            org.freemarker
            freemarker
            2.3.30
        
        
            com.baomidou
            mybatis-plus-generator
            3.4.1
        

        
            org.apache.shiro
            shiro-spring
            1.7.1
        
        
            org.springframework.boot
            spring-boot-starter-aop
        
        
            org.crazycake
            shiro-redis
            2.8.24
        

        
            org.apache.commons
            commons-lang3
            3.9
        

        
            io.springfox
            springfox-swagger2
            2.9.2
        
        
            io.springfox
            springfox-swagger-ui
            2.9.2
        

将用户信息交给redis管理
使用swagger2

2.shiro配置类
import com.example.shirospringboot.realm.UserRealm;
import com.example.shirospringboot.shiro.MySessionManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.linkedHashMap;
import java.util.Map;




@Configuration
public class ShiroConfig {

    @Autowired
    private RedisManager redisManager;
    @Autowired
    private RedisSessionDAO redisSessionDAO;
    @Autowired
    private RedisCacheManager redisCacheManager;

    @Bean()
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        
        Map filterChainDefinitionMap =new linkedHashMap<>();
        filterChainDefinitionMap.put("/login","anon");
        filterChainDefinitionMap.put("/logout","anon");
        
        //放行Swagger2页面,需要放行这些
        filterChainDefinitionMap.put("/swagger-ui.html","anon");
        filterChainDefinitionMap.put("/swagger
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        autoProxyCreator.setProxyTargetClass(true);
        return autoProxyCreator;
    }
}
3.ShiroRedis配置类

application.properties

spring.redis.shiro.host=127.0.0.1
spring.redis.shiro.port=6379
spring.redis.shiro.timeout=5000
#没有密码写了会报错
#spring.redis.shiro.password=123456
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class ShiroRedisConfig {
    @Value("${spring.redis.shiro.host}")
    private String host;
    @Value("${spring.redis.shiro.port}")
    private int port;
    @Value("${spring.redis.shiro.timeout}")
    private int timeout;
//    @Value("${spring.redis.shiro.password}")
//    private String password;

    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setExpire(1800);// 配置缓存过期时间
        redisManager.setTimeout(timeout);
//        redisManager.setPassword(password);
        return redisManager;
    }
    @Bean
    public RedisCacheManager redisCacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }
}

4.Swagger配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.documentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@EnableSwagger2
@Configuration
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {
        return new Docket(documentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                
                .apis(RequestHandlerSelectors.basePackage("com.example.shirospringboot.controller"))
                
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Shiro-Springboot Swagger2 APIs")
                .description("shiro-springboot项目controller接口文档")
                .version("1.0")
                .build();
    }
}
5.安全认证和权限验证的核心,自定义Realm
import com.example.shirospringboot.entity.User;
import com.example.shirospringboot.service.impl.UserServiceImpl;
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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.Set;

public class UserRealm extends AuthorizingRealm {
    @Autowired
    private UserServiceImpl userService;

    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        
        String username = (String) token.getPrincipal();
        User user = userService.getUserByUserName(username);
        if(user==null){
            throw new AuthenticationException();
        }
        String credentials = user.getPassword();
        ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user.getUsername(), //用户名
                credentials, //密码
                credentialsSalt,
                getName()  //当前realm对象的name,调用父类的getName()方法即可
        );
        Session session = SecurityUtils.getSubject().getSession();
        session.setAttribute("USER_SESSION", user);
        return authenticationInfo;

        
    }
    
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Session session = SecurityUtils.getSubject().getSession();
        System.out.println("授权方法获取用户:"+session.getAttribute("USER_SESSION"));
        User user = (User) session.getAttribute("USER_SESSION");
        Set roleSet = new HashSet<>();
        Set permissionSet = new HashSet<>();
        userService.getUserRoles(user.getUsername()).forEach(role -> roleSet.add(role.getName()));
        userService.getUserPermissions(user.getUsername()).forEach(permission -> permissionSet.add(permission.getName()));
        info.setRoles(roleSet);
        info.setStringPermissions(permissionSet);
        return info;
    }
}
6.全局异常处理器
import com.example.shirospringboot.Result.Result;
import com.example.shirospringboot.Result.ResultUtil;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {
    

    //无权限
    @ExceptionHandler(value = UnauthorizedException.class)
    public Result handler(UnauthorizedException e) {
        return ResultUtil.NO_PERMISSION();
    }
    //身份过期
    @ExceptionHandler(value = ExpiredCredentialsException.class)
    public Result handler(ExpiredCredentialsException e) {
        return ResultUtil.IDENTITY_EXPIRED();
    }
    //没有登陆
    @ExceptionHandler(value = UnauthenticatedException.class)
    public Result handler(UnauthenticatedException e) {
        return ResultUtil.NOT_LOGGED_IN();
    }
    //密码错误
    @ExceptionHandler(value = IncorrectCredentialsException.class)
    public Result handler(IncorrectCredentialsException e) {
        return ResultUtil.INCORRECT_PASSWORD();
    }
    //用户不存在
    @ExceptionHandler(value = AuthenticationException.class)
    public Result handler(AuthenticationException e) {
        return ResultUtil.USER_NOT_FOUND();
    }
    //账号冻结
    @ExceptionHandler(value = LockedAccountException.class)
    public Result handler(LockedAccountException e) {
        return ResultUtil.ACCOUNT_FREEZING();
    }

}

7.因为现在的项目大多都是前后端分离的,所以我们需要实现自己的session管理
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;


public class MySessionManager extends DefaultWebSessionManager {

    private static final String TOKEN = "token";
    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    public MySessionManager() {
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader(TOKEN);
        //如果请求头中有 token 则其值为sessionId
        if (!StringUtils.isEmpty(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            //否则按默认规则从cookie取sessionId
            return super.getSessionId(request, response);
        }
    }
}

8.控制器
import com.example.shirospringboot.Result.Result;
import com.example.shirospringboot.Result.ResultUtil;
import com.example.shirospringboot.entity.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;

@Api(value="LoginController|登录控制器")
@RestController
@RequestMapping("/")
public class LoginController {
    @ApiOperation(value = "用户登录接口", notes = "提供用户名和密码")
    @PostMapping("/login")
    public Result login(@ApiParam(name = "user",value = "登录用户",required = true) @RequestBody User user){
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
        Subject subject = SecurityUtils.getSubject();
        HashMap map = new HashMap<>();
        try {
            subject.login(token);
            map.put("token", subject.getSession().getId());
        } catch (IncorrectCredentialsException e) {
            //密码错误
            throw e;
        } catch (LockedAccountException e) {
            //冻结
            throw e;
        } catch (AuthenticationException e) {
            //用户不存在
            throw e;
        } catch (Exception e) {
            return ResultUtil.EXCEPTION_UNKNOWN();
        }
        System.out.println(user.getUsername()+"已登录");
        return ResultUtil.SUCCESS("登陆成功",null);
    }

    @ApiOperation("用户登出接口")
    @GetMapping("/logout")
    public Result logout(){
        SecurityUtils.getSubject().logout();
        return ResultUtil.SUCCESS("已退出登录",null);
    }

    @ApiOperation("用户权限测试接口")
    @GetMapping("/test")
    @RequiresRoles(value = {"admin"})
    @RequiresPermissions(value = {"order:query:zhujie"})
    public Result test(){
        return ResultUtil.SUCCESS("test",null);
    }

}

常用注解
@RequiresGuest 代表无需认证即可访问,同理的就是 /path=anon
@RequiresAuthentication 需要认证,只要登录成功后就允许你操作
@RequiresPermissions 需要特定的权限,没有则抛出 AuthorizationException
@RequiresRoles 需要特定的橘色,没有则抛出 AuthorizationException

9.以上就是shiro登陆和鉴权的主要配置和类,下面补充一下其他信息。

因为数据是模拟的,所以在登陆认证的时候,并没有通过数据库查用户信息,可以通过以下方式模拟加密后的密码:

         String name="zhujie",password="123",salt= SaltUtil.getSalt();
         System.out.println("salt:"+salt);  //数据库salt字段
         ByteSource saltByteSource = ByteSource.Util.bytes(salt);
         String newPs = new SimpleHash("MD5", password, salt, 1024).toHex();
         System.out.println("saltByteSource:"+saltByteSource);
         System.out.println("密码:"+newPs);  // 数据库password字段
    }

UserService添加两个方法,便于调用。

public interface IUserService extends IService {
    User getUserByUserName(String username);
    List getUserRoles(String username);
    List getUserPermissions(String username);
}
    }

随机生成盐

import java.util.Random;

public class SaltUtil {
    static char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()_+".toCharArray();
    static int length=32;

    public static String getSalt(int saltLength){
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < saltLength; i++){
            
            char aChar = chars[new Random().nextInt(chars.length)];
            sb.append(aChar);
        }
        return sb.toString();
    }
    public static String getSalt(){
        return getSalt(length);
    }
}

统一结果封装

import io.swagger.annotations.ApiModel;
import lombok.Data;

@Data
@ApiModel("统一封装返回对象")
public class Result {
    
    private Integer code;
    
    private String msg;
    
    private T data;
}
public enum ResultEnum {
    SUCCESS(200, "操作成功"),
    EXCEPTION_NOT_LOGGED_IN(401, "未登录,请登录"),
    EXCEPTION_USER_NOT_FOUND(401,"用户不存在"),
    EXCEPTION_INCORRECT_PASSWORD(401,"密码错误"),
    EXCEPTION_IDENTITY_EXPIRED(401, "身份已过期,请重新登录"),
    EXCEPTION_ACCOUNT_FREEZING(401, "账号已被冻结"),
    EXCEPTION_NO_PERMISSION(402, "无权限操作"),
    EXCEPTION_UNKNOWN(400, "未知异常");

    private Integer code;
    private String msg;

    ResultEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public Integer getCode() {
        return code;
    }
    public String getMsg() {
        return msg;
    }
}

import org.apache.commons.lang3.StringUtils;

public class ResultUtil {
    
    public static Result SUCCESS(String message,Object object) {
        Result result = new Result();
        result.setCode(ResultEnum.SUCCESS.getCode());
        if(StringUtils.isBlank(message)){
            result.setMsg(ResultEnum.SUCCESS.getMsg());
        }else{
            result.setMsg(message);
        }
        result.setData(object);
        return result;
    }
    public static Result SUCCESS() {
        return SUCCESS(null,null);
    }
    
    public static Result NOT_LOGGED_IN() {
        Result result = new Result();
        result.setCode(ResultEnum.EXCEPTION_NOT_LOGGED_IN.getCode());
        result.setMsg(ResultEnum.EXCEPTION_NOT_LOGGED_IN.getMsg());
        return result;
    }
    
    public static Result USER_NOT_FOUND() {
        Result result = new Result();
        result.setCode(ResultEnum.EXCEPTION_USER_NOT_FOUND.getCode());
        result.setMsg(ResultEnum.EXCEPTION_USER_NOT_FOUND.getMsg());
        return result;
    }
    
    public static Result INCORRECT_PASSWORD() {
        Result result = new Result();
        result.setCode(ResultEnum.EXCEPTION_INCORRECT_PASSWORD.getCode());
        result.setMsg(ResultEnum.EXCEPTION_INCORRECT_PASSWORD.getMsg());
        return result;
    }
    
    public static Result IDENTITY_EXPIRED() {
        Result result = new Result();
        result.setCode(ResultEnum.EXCEPTION_IDENTITY_EXPIRED.getCode());
        result.setMsg(ResultEnum.EXCEPTION_IDENTITY_EXPIRED.getMsg());
        return result;
    }
    
    public static Result NO_PERMISSION() {
        Result result = new Result();
        result.setCode(ResultEnum.EXCEPTION_NO_PERMISSION.getCode());
        result.setMsg(ResultEnum.EXCEPTION_NO_PERMISSION.getMsg());
        return result;
    }
    
    public static Result ACCOUNT_FREEZING() {
        Result result = new Result();
        result.setCode(ResultEnum.EXCEPTION_ACCOUNT_FREEZING.getCode());
        result.setMsg(ResultEnum.EXCEPTION_ACCOUNT_FREEZING.getMsg());
        return result;
    }
    
    public static Result EXCEPTION_UNKNOWN() {
        Result result = new Result();
        result.setCode(ResultEnum.EXCEPTION_UNKNOWN.getCode());
        result.setMsg(ResultEnum.EXCEPTION_UNKNOWN.getMsg());
        return result;
    }
}

10.出现的问题 问题1








添加以上依赖后,从缓存中读取用户信息会出现cast异常,所以不添加该热部署依赖即可

 User user = (User) session.getAttribute("USER_SESSION");
问题2

放行Swagger2页面

        filterChainDefinitionMap.put("/swagger-ui.html","anon");
        filterChainDefinitionMap.put("/swagger/**","anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**","anon");
        filterChainDefinitionMap.put("/v2/**","anon");
        filterChainDefinitionMap.put("/static/**", "anon");
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/489099.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号