# String-security(配置异常处理器,封装JWT工具类)

1 JWT是做什么的?

为了在前后端分离项目中使用 JWT ,我们需要达到 2 个目标:

  • 在用户登录认证成功后,需要返回一个含有 JWT token 的 json 串。

  • 在用户发起的请求中,如果携带了正确合法的 JWT token ,后台需要放行,运行它对当前 URI 的访问

在spring security项目中添加nimbus坐标即可

  <!--nimbus坐标-->
<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
    <version>9.11.1</version>
</dependency>

2 1、返回 JWT token

Spring Security 中的登录认证功能是由 UsernamePasswordAuthenticationFilter 完成的,默认情况下,在登陆成功后,接下来就是页面跳转,显示你原本想要访问的 URI( 或 / ),现在,我们需要返回 JSON(其中还要包含 JWT token )。

Spring Security 支持通过实现 AuthenticationSuccessHandler 接口,来自定义在登陆成功之后你所要做的事情(之前有讲过这部分内容):

http.formLogin()
    .successHandler(new JWTAuthenticationSuccessHandler());

当用户登录成功后,Spring security返回一个jwt的token给客户端,所以要在Spring security登录成功的过滤器中实现jwt token的生成代码。

  • 创建spring security项目并添加nimbus坐标

    关键坐标如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.woniu</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.4</version>
    </parent>

    <dependencies>
        <!--springmvc启动器-->
        <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>
        </dependency>

        <!--springboot整合security坐标-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!--nimbus坐标-->
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>9.11.1</version>
        </dependency>
    </dependencies>
</project>

配置认证UserDetailsService

/**
 * spring security认证业务类
 */
@Service
public class LoginUserDetailsService implements UserDetailsService {
    @Resource
    private UserDao userDao;
    //为passwordEncoder注入值
    @Resource
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //调用dao到数据库中根据username查找用户信息
        Users users = userDao.getByUserName(username);
        try {
            //将查找到的用户帐号与密码保存到Security的User对象中由Security进行比对
            return new User(users.getUsername(), users.getPassword(),
                    AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,user:insert,user:delete")); //配置登录用户有哪些角色和权限,此处模拟直接写死
        }catch (Exception e){
            throw  new UsernameNotFoundException("用户"+username+"不存在");
        }
    }
}

spring security配置类

package com.example.springsecurity3.configuration;

import com.example.springsecurity3.hndler.*;
import com.example.springsecurity3.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class UsersConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityService securityService;

        @Bean
        public PasswordEncoder getPassword(){
            return new BCryptPasswordEncoder();
        }

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //给密码加密
        auth.userDetailsService(securityService).passwordEncoder(getPassword());
    }

    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().//告诉security,要是用自己的页面
                loginPage("/login.html").//告诉security页面在哪里
                loginProcessingUrl("/dologin").//告诉security表但提交地址(
                //hasAuthority,hasAnyAuthority,hasRole,hasAnyRole四种方法可以设置访问路径,现在我们用PreAuthorize("hasAuthority('admin')")注解就可以替我们
                //访问获取授权,在UserConfig类上加上EnableGlobalMethodSecurity(securedEnabled=true,proPostEnable=true)
              //注册登录成功处理器
               successHandler(new SecurityHandler()).
                //注册登录失败处理器
                failureHandler(new LoginFailHandler()).
                permitAll();//将登陆的权限放出来
        //注册未登录处理器,
        http.exceptionHandling().authenticationEntryPoint(new MyAuthenticationEntryPoint())
                //注册有用户名和密码,没有授权的处理器
                .accessDeniedHandler(new MyAccessDeniedHandler());
        //开启访问权限
        http.cors();
        //注册退出
        http.logout().logoutSuccessHandler(new LogoutSuccess());
        //配置权限请求
//        http.authorizeRequests().anyRequest().authenticated();//关闭所有请求
//        http.csrf().disable();//关闭跨站脚本攻击
        //配置权限请求
        http.authorizeRequests().
                anyRequest().
                authenticated();//关闭所有请求
        http.csrf().disable();//关闭跨站脚本攻击
    }
}

配置登录结果异常处理器
登录成功
package com.example.springsecurity3.hndler;

import com.alibaba.fastjson.JSON;
import com.example.springsecurity3.utils.JWTUtils;
import com.example.springsecurity3.utils.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 登录成功提示信息
 */
public class SecurityHandler implements AuthenticationSuccessHandler {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;



    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        User user = (User) authentication.getPrincipal();
        //把jwt放入redis,并设置有效时间
//        try {
//            redisTemplate.opsForValue().set("jwt:"+user.getUsername(),
//                    JWTUtils.createJWT(user.getUsername()));
           // redisTemplate.opsForValue().set("jwt:"+user.getUsername(),JWTUtils.createJWT(user.getUsername()));
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
        System.out.println(user.getUsername());
        response.setContentType("application/json;charset=utf-8");
        PrintWriter pw = response.getWriter();
            //将json转成字符串
        String json = JSON.toJSONString(ResponseResult.SECCUSS);
        pw.print(json);
        pw.flush();
        pw.close();
    }

    }


在这里插入图片描述

登录失败
package com.example.springsecurity3.hndler;

import com.alibaba.fastjson.JSON;
import com.example.springsecurity3.utils.ResponseResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 登陆失败,后提示信息
 */
public class LoginFailHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter pw = response.getWriter();

        String json = JSON.toJSONString(ResponseResult.FAIL );
        pw.print(json);
        pw.flush();
        pw.close();
    }
}

在这里插入图片描述

未登录访问时,提示请先登录
package com.example.springsecurity3.hndler;

import com.alibaba.fastjson.JSON;
import com.example.springsecurity3.utils.ResponseResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 前后端分离项目,用户未登录直接访问系统资源,会被该类拦截
 */
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter pw = response.getWriter();

        String json = JSON.toJSONString(ResponseResult.NOTLOGIN );
        pw.print(json);
        pw.flush();
        pw.close();
    }
}

在这里插入图片描述

有用户名和密码,没有权限时提示请联系管理员
package com.example.springsecurity3.hndler;

import com.alibaba.fastjson.JSON;
import com.example.springsecurity3.utils.ResponseResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 虽然知道用户名和密码,但是你没有权限访问就会拦截呢没有权限的访问操作
 */
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter pw = response.getWriter();

        String json = JSON.toJSONString(ResponseResult.NOTAUTH );
        pw.print(json);
        pw.flush();
        pw.close();
    }
}

在这里插入图片描述

退出成功提示
package com.example.springsecurity3.hndler;

import com.alibaba.fastjson.JSON;
import com.example.springsecurity3.utils.ResponseResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 用户退出操作
 */
public class LogoutSuccess implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter pw = response.getWriter();

        String json = JSON.toJSONString(ResponseResult.LOGOUT );
        pw.print(json);
        pw.flush();
        pw.close();
    }
}

在这里插入图片描述

创建JWT工具类

package com.example.springsecurity3.utils;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import org.springframework.beans.factory.SmartInitializingSingleton;

import java.text.ParseException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

/**
 * jet工具类
 * 1:创建jwt
 * 2:校验jwt是否合法
 * 3:返回载荷部分
 */
public class JWTUtils {


  public  static final  String KEY="adbaobcabcnpncinnpcnpnpnc";
  //随机生成32位的字符串
  //public static final String KEY=UUID.randomUUID().toString

     public static  String createJWT(String username)throws Exception{
         //创建JWT头部
         JWSHeader jwsHeader=new JWSHeader.
                 Builder(JWSAlgorithm.EdDSA.HS256).
                 type(JOSEObjectType.JWT).build();
         //创建载荷,储存用户信息
         Map  map=new  HashMap<>();
        map.put("username",username);
         Payload payload = new Payload(map);

         MACSigner jwsSigner = new MACSigner(KEY);
         //3.2创建类将头部和载荷加在一起组成新的字符串
         JWSObject jwsObject=new JWSObject(jwsHeader,payload);
         //3.3根据秘钥将jwsObject加密
        jwsObject.sign(jwsSigner);
        return  jwsObject.serialize();
     }

    /**
     * 接收用户传入的jwt字符串,根据秘钥进行解码,对比用户传入的jwt是正确的
     * 第一步:从页面拿到jwt字符串
     * 第二部:将jwt字符串成一个对象
     * 第三步:调用verify()方法,传一个秘钥看看能不能解析出来
     * JSON.parseObject 将字符串转成对象 JSON.toJSONString 将对象转成字符串
     * @param jwt
     * @return
     * @throws Exception
     */
     public static boolean getDecrypt (String jwt)throws Exception{


         //把jwt字符串转成jwt对象
         JWSObject jwsObject = JWSObject.parse(jwt);
         //把jwt对象通过秘钥解密
         MACVerifier macVerifier = new MACVerifier(KEY);
         //解密
         boolean verify = jwsObject.verify(macVerifier);
         return verify;

     }

    /**
     * 获取载荷内容
     * @param jwt
     * @return
     * @throws Exception
     */
    public  static Map getAccount(String jwt) throws Exception {
        //获取jet对象
        JWSObject parse = JWSObject.parse(jwt);
        //获取用户信息
        return  parse.getPayload().toJSONObject();

    }
}

登录成功使用redis数据库储存用户信息

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值