Shiro+JWT实现前后端分离登录验证

Shiro+JWT实现前后端分离登录验证

  1. 导入相关的jar包
	<dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.7.1</version>
    </dependency>
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.16.0</version>
    </dependency>
注意:shiro整合JWT导包不能导shiro-spring-boot-web-starter,会报错
  1. 创建一个用来生成token的工具类

public class JWTUtils {
    public static final long EXPIRE = 1000 * 60 * 60 * 24;  //过期时间一天
    public static final String SECRET = "ABCDEFGH";//这里设置你的盐,至关重要,不要透露给其他人

    public static String create(Map<String, String> map) {
        JWTCreator.Builder builder = JWT.create();
        map.forEach((K, V) -> {
            builder.withClaim(K, V);     //遍历map将用户数据放入负载中
        });
       	Date now = new Date();
        Date expires = new Date(now.getTime() + EXPIRE);
        builder.withIssuedAt(now);  //设置创建时间
        builder.withExpiresAt(expires);     //设置过期时间
        String uuid = UUID.randomUUID().toString().replaceAll("-","");	//通过UUID生成一个唯一标识
        builder.withJWTId(uuid);    //设置唯一标识
        return builder.sign(Algorithm.HMAC256(SECRET));
    }

    public static boolean verify(String token) {
        try {
        	JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);  // 如果验证通过,则不会把报错,否则会报错
            return true;
        } catch (TokenExpiredException e) {//令牌过期异常
            return false;	
        } catch (SignatureVerificationException e) {//签名不一致异常
            return false;
        } catch (AlgorithmMismatchException e) {//算法不匹配异常
            return false;
        } catch (InvalidClaimException e){//失效的payload异常
            return false;
        }catch (Exception e) {
            return false;
        }
    }


    public static DecodedJWT getToken(String token) {
    	//解析传来的字符串
        return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
    }
}

  1. 封装一个类,继承shiro中的token,里面放token字符串信息
//这个就类似UsernamePasswordToken
//不需要用户名和密码,因为需要的信息以及放在token中了,只要对token进行判断即可
public class JwtToken implements AuthenticationToken {

    private String jwt;

    public JwtToken(String jwt) {
        this.jwt = jwt;
    }

    @Override//类似是用户名
    public Object getPrincipal() {
        return jwt;
    }

    @Override//类似密码
    public Object getCredentials() {
        return jwt;
    }
    //返回的都是jwt
}
  1. 创建一个过滤器,用于拦截请求,拿到请求头中的token,交给shiro的realm做处理
public class JWTFilter extends AccessControlFilter {

    /*
     * 1. 返回true,shiro就直接允许访问url
     * 2. 返回false,shiro才会根据onAccessDenied的方法的返回值决定是否允许访问url
     * */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        //这里先让它始终返回false来使用onAccessDenied()方法
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        //从请求头中获取token
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String jwt = request.getHeader("token");
        JwtToken jwtToken = new JwtToken(jwt);
        /*
         * 下面就是固定写法
         * */
        try {
            // 委托 realm 进行登录认证
            //所以这个地方最终还是调用JwtRealm进行的认证
            getSubject(servletRequest, servletResponse).login(jwtToken);
            //也就是subject.login(token)
        } catch (Exception e) {
            e.printStackTrace();
            //抛出任何异常都调用错误方法
            onLoginFail(servletResponse);
            //调用下面的方法向客户端返回错误信息
            return false;
        }

        return true;
        //执行方法中没有抛出异常就表示登录成功
    }

    //登录失败时默认返回 -1 状态码
    private void onLoginFail(ServletResponse response) throws IOException {
    	//返回json给前端
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        httpResponse.setCharacterEncoding("UTF-8");

        httpResponse.setContentType("application/json; charset=utf-8");
        httpResponse.getWriter().write("{\"success\":-1}");
    }
}

  1. 在realm中,对过滤器传来的token进行判断
public class JwtRealm extends AuthorizingRealm {
    @Autowired
    private IAdministratorService adminService;

    /*
     * 多重写一个support
     * 标识这个Realm是专门用来验证JwtToken
     * 不负责验证其他的token(UsernamePasswordToken)
     * */
    @Override
    public boolean supports(AuthenticationToken token) {
        //这个token就是从JWTFilter过滤器中传入的jwtToken
        return token instanceof JwtToken;
    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    	//根据业务自行授权
        return null;
    }

    //认证
    //这个token就是从过滤器中传入的token
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String jwt = (String) token.getPrincipal();
        if (jwt == null) {
            throw new NullPointerException("token 不允许为空");
        }

        //判断
        if (!JWTUtils.verify(jwt)) {//判断token是否正确
            throw new UnknownAccountException();
        }
        
        //下面是验证这个admin是否是真实存在的
        DecodedJWT decodedJWT = JWTUtils.getToken(jwt);//解析token
        String username = decodedJWT.getClaim("username").asString();//判断数据库中username是否存在
        
        QueryWrapper<Administrator> administratorQueryWrapper = new QueryWrapper<>();
        administratorQueryWrapper.eq("user_name",username);
        Administrator dbAdmin = adminService.getOne(administratorQueryWrapper);

        if (dbAdmin == null){
            throw new UnknownAccountException();
        }

        return new SimpleAuthenticationInfo(jwt,jwt,getName());
        //这里返回的是类似账号密码的东西,但是jwtToken都是jwt字符串。不需要进行账号密码的比对,因为上面对token已经进行判断了,token被修改了是无法进入的
    }
}
  1. 配置shiro一些环境
//springBoot整合jwt实现认证有三个不一样的地方,对应下面abc
@Configuration
public class ShiroConfig {

    @Bean
    public Realm realm() {
        return new JwtRealm();
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());
        /*
         * b
         */
        // 关闭 ShiroDAO 功能
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        // 不需要将 Shiro Session 中的东西存到任何地方(包括 Http Session 中)
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager());
        /*
         * c. 添加jwt过滤器,并在下面注册
         * 也就是将jwtFilter注册到shiro的Filter中
         * 指定除了login和logout之外的请求都先经过jwtFilter
         * */
        Map<String, Filter> filterMap = new HashMap<>();
        //这个地方其实另外两个filter可以不设置,默认就是
        filterMap.put("jwt", new JWTFilter());
        shiroFilter.setFilters(filterMap);

        // 拦截器
        Map<String, String> filterRuleMap = new LinkedHashMap<>();
        //anon代表不拦截
        filterRuleMap.put("/login", "anon");
        filterRuleMap.put("/logout", "logout");
        filterRuleMap.put("/queryProcess","anon");//放行此请求
        filterRuleMap.put("/**", "jwt");//所有请求都拦截
        shiroFilter.setFilterChainDefinitionMap(filterRuleMap);

        return shiroFilter;
    }
}
  1. 在controller中进行登录判断,并返回token给前端
	@PostMapping("/login")
    public JSONObject login(@RequestBody String param) {
        JSONObject json = JSONObject.parseObject(param);
        if (json==null){
            json.put("success",0);
            return json;
        }

        String username = json.getString("username");
        String password = json.getString("password");
        json.clear();

        if (username==null||password==null){
            json.put("success",0);
            return json;
        }

        QueryWrapper<Administrator> adminQueryWrapper = new QueryWrapper<>();
        adminQueryWrapper.eq("user_name",username);
        adminQueryWrapper.eq("password",password);
        Administrator dbAdmin = administratorService.getOne(adminQueryWrapper);

        if (dbAdmin==null){
            json.put("success",0);
            return json;
        }

        Map<String, String> chaim = new HashMap<>();
        chaim.put("username", dbAdmin.getUserName());
        String token = JWTUtils.create(chaim);

        json.put("success",1);
        json.put("username",username);
        json.put("token",token);
        return json;
    }

    @RequestMapping("/testdemo")
    public ResponseEntity<String> testdemo() {
        return ResponseEntity.ok("我爱蛋炒饭");
    }

在权限认证那一块,可以根据自己的业务进行设置和判断

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: springboot+shiro+jwt 是一种常见的后端技术组合,其中 springboot 是一个基于 Spring 框架的快速开发框架,shiro 是一个安全框架,用于身份验证、授权和加密等功能,jwt 是一种基于 token 的身份验证机制,可以用于前后端分离的应用中。这种技术组合可以帮助开发者快速搭建安全可靠的后端服务。 ### 回答2: Springboot是一个开源的Java开发框架,是基于Spring框架的远程服务器控制技术方案,是现代化的全栈框架,集成丰富,提供了大量的开箱即用的组件,可以大大简化开发人员的开发工作。 Shiro是一个强大的轻量级安全框架,支持用户身份识别、密码加密、权限控制、会话管理等一系列安全功能,被广泛应用于JavaWeb开发中。 JWT(JSON Web Token)是一种开放标准(RFC 7519),定义了一种简洁的、自包含的方式,用于通信双方之间以JSON对象的形式安全地传递信息。JWT可以用于状态管理和用户身份认证等场景。 在使用SpringBoot开发Web应用过程中,ShiroJWT可以同时用于用户身份认证和权限控制。Shiro提供了一系列的身份识别、密码加密、权限控制、会话管理等功能,而JWT则可以实现无状态的身份认证。使用ShiroJWT,可以有效地保护Web应用的安全,避免被恶意攻击者利用。 具体而言,使用ShiroJWT可以将用户认证的主要逻辑统一在一起,实现更加优雅的认证和授权过程。同时,这样的组合也可以避免一些常见的安全漏洞,比如会话劫持、XSS攻击、CSRF等。 在实际开发中,使用SpringBoot Shiro JWT可以方便地进行二次开发,进一步优化开发成本和提高开发效率。同时,使用这个组合可以让开发者更好地专注于业务逻辑的开发,而无需关注安全问题,从而提高开发质量和开发人员的工作效率。 ### 回答3: Spring Boot是一种基于Spring框架的快速开发微服务的工具,能够使开发者可以快速构建基于Spring的应用程序。而Shiro是一个强大易用的Java安全框架,可用于身份验证、权限控制、加密等。JWT(JSON Web Token)是一种基于JSON的安全令牌,可用于在客户端和服务器之间传递信息。 在使用Spring Boot开发Web应用程序时,通常需要进行用户身份验证和访问控制,这时候就可以使用Shiro实现这些功能。同时,由于Web应用程序需要跨域访问,因此使用JWT可以方便地实现身份验证和授权的管理。 在使用Spring Boot和Shiro时,可以使用JWT作为身份验证和授权的管理工具。此时,需要进行以下几个步骤: 1.添加ShiroJWT的依赖 在Spring Boot项目中,可以通过Maven或Gradle等工具添加ShiroJWT的依赖。例如,可以添加以下依赖: <!-- Shiro依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.7.0</version> </dependency> <!-- JWT依赖 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> 2.配置ShiroSpring Boot项目中,可以通过在application.properties或application.yml文件中进行Shiro的配置。例如,可以配置以下内容: # Shiro配置 shiro: user: loginUrl: /login # 登录页面URL jwt: secret: my_secret # JWT加密密钥 expiration: 1800000 # JWT过期时间,单位为毫秒,默认30分钟 3.创建Shiro的Realm 在Shiro中,Realm是用于从应用程序中获取安全数据(如用户、角色和权限)的组件。因此,需要创建Shiro的Realm,用于管理用户的认证和授权。 例如,可以创建一个自定义的Realm,其中包括从数据库中获取用户和角色的方法: public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; /** * 认证,验证用户身份 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); User user = userService.findByUsername(username); if (user == null) { throw new UnknownAccountException("用户名或密码错误"); } String password = user.getPassword(); return new SimpleAuthenticationInfo(user.getUsername(), password, getName()); } /** * 授权,验证用户的访问权限 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); User user = userService.findByUsername(username); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); Set<String> roles = user.getRoles(); authorizationInfo.setRoles(roles); Set<String> permissions = user.getPermissions(); authorizationInfo.setStringPermissions(permissions); return authorizationInfo; } } 4.使用JWT进行身份验证和授权管理 在Spring Boot应用程序中,使用ShiroJWT来进行身份验证和授权管理的流程大致如下: 4.1 前端用户进行登录操作,将用户名和密码发送到后台服务。 4.2 后台服务进行身份验证,将用户身份信息生成JWT并返回给前端。 4.3 前端保存JWT,并在后续的请求中将其发送到后台服务。 4.4 后台服务验证JWT的有效性,并根据用户的角色和权限信息进行访问控制。 综上所述,Spring Boot、ShiroJWT可以很好地配合使用,实现Web应用程序的身份验证和授权管理。这种组合方案可以提高开发效率和系统安全性,同时也适用于微服务架构中对身份验证和授权的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值