首页技术栈归档照片墙音乐日记随想收藏夹友链留言关于

Vue+JWT+elementUI+Spring Security实现登录校验

写作时间:2026-07-08

Vue+JWT+elementUI+Spring Security实现登录校验

前端vue

下载依赖

#安装cookies
npm install --save js-cookie
#安装axios
npm install --save axios vue-axios
#安装elementUI
npm i element-ui -S
#安装路由
#npm install vue-router@3
npm install vue-router@3 or 4    

main.js

import Vue from 'vue'
import App from './App'
import router from "./router/index"
Vue.config.productionTip = false
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import axios from "axios";
import request from "./router/request"

//注册axios为全局的变量
Vue.prototype.$axios=axios;
Vue.use(ElementUI)
//导入拦截,注册未全部变量
Vue.prototype.$request=request;
/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  request,
  components: { App },
  template: '<App/>'
})

App.vue

<template>
  <div id="app">
  <router-view></router-view>
  </div>
</template>

<script>

export default {
  name: 'App',
  components: {
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

router目录下index.js

import Vue from "vue"
import Router from "vue-router";
Vue.use(Router);

const router=new Router({
  routes:[
    {
      name:"Login",
      path:'/',
      component: ()=>import("@/components/Login")
     },
      {
          name:"Main",
          path: "/Main",
          component:()=>import("@/components/Main")
      }

      ]
});

//配置导航守卫
router.beforeEach((to, from,next) => {
  console.log("to======>"+to.path);
  console.log("from======>"+from.path);
  const isLogin = true;
  if(to.path!=="Login"){
    if(isLogin){
      next();
    }
    return false;
  }
  return next();
});
export default router;

request.js

import axios from "axios";
//导入cookie
import cookie from "js-cookie"
/*创建axios实例*/
const intance=axios.create({
  baseURL : "http://localhost:9999",
  timeout:10000
})

/*请求的拦截*/
intance.interceptors.request.use(function (config) {
  config.headers.token=cookie.get("token")
  return config;
},function (error) {
  console.log(error)
  return Promise.reject(error)
})

/*响应的拦截器*/
intance.interceptors.response.use(function (response) {
  const  res=response.data
  return  res;
},function (error) {
  return Promise.reject(error)
})

export default intance;

Login.vue

<template>
    <div style="width: 300px">
        <el-form ref="loginForm" :model="form" :rules="rules" label-width="80px" class="login-box">
            <h3 class="login-title">欢迎登录</h3>
            <el-form-item label="账号" prop="username">
                <el-input type="text" placeholder="请输入账号" v-model="form.username"/>
            </el-form-item>
            <el-form-item label="密码" prop="password">
                <el-input type="password" placeholder="请输入密码" v-model="form.password"/>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" v-on:click="onsubmit('loginForm')">登录</el-button>
            </el-form-item>
        </el-form>
        <el-dialog title="温馨提示"  width="30%">
            <span>请输入账号和密码</span>
            <span slot="footer" class="dialog-footer">
          <el-button type="primary" @click="dialogVisible = false">确定</el-button>
        </span>
        </el-dialog>
    </div>
</template>

<script>
  import cookie from "js-cookie"
    export default {
        name: "Login",
        data(){
            return{
                form:{
                    username:'',
                    password:'',
                },
                //表单验证,需要在 el-form-item 元素中增加prop属性
                rules:{
                    username:[
                        {required:true,message:"账号不可为空",trigger:"blur"}
                    ],
                    password:[
                        {required:true,message:"密码不可为空",tigger:"blur"}
                    ]
                },

                //对话框显示和隐藏
                dialogVisible:false
            }
        },
        methods:{
            onsubmit(formName){
                //为表单绑定验证功能
                this.$refs[formName].validate((valid)=>{
                    if(valid){
                        this.$request.post("/user/doLogin?username="+this.form.username+"&password="+this.form.password)
                          .then((response)=>{
                            if (response!=="20001"){
                             // console.log(response.data.token)
                             // 第一个参数存储的token名,第二个参数是服务器返回的token值,第三个参数设置token的作用域
                             cookie.set("token",response.data.token,{admin:"localhost"})//设置token
                             console.log(cookie.get("token"))
                            this.$router.push('/Main');
                            }else {
                              alert(response.success)
                                alert("用户名或密码不正确")
                            }
                        })

                    }else{
                        this.dialogVisible=true;
                        return false;
                    }
                });
            }
        }
    }
</script>

<style scoped>

</style>

Main.vue

<template>
    <div>
        <h2>新闻列表</h2>
        <el-row class="newssavebtn">
            <el-button type="primary" @click="newsAdd()" icon="el-icon-plus" >新增</el-button>
        </el-row>
        <el-table
                :data="newsList"
                style="width: 100%">
            <el-table-column
                    prop="title"
                    label="标题"
                    width="180">
            </el-table-column>
            <el-table-column
                    prop="publisher"
                    label="发布人">
            </el-table-column>
            <el-table-column
                    prop="createTime"
                    label="发布时间">
            </el-table-column>
            <el-table-column label="操作">
                <!--template slot-scope="scope":存的是后台的数据-->
                <template slot-scope="scope">
                    <!--scope.$index:获取索引,
                       scope.row:获取所有数据
                       scope.row.id:获取id,如果实现删除功能就写  @click="handleDelete(scope.row.id)
                       -->
                    <el-button
                            size="mini"
                            @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
                    <el-button
                            size="mini"
                            type="danger"
                            @click="newsDelete(scope.row.id)">删除</el-button><!--scope.row.id:获取删除新闻id-->
                </template>
            </el-table-column>
        </el-table>
        <!--分页-->
        <el-pagination
                background
                layout="prev, pager, next"
                @current-change="handPage"
                :page-size="pageSize"
                :current-page="pageNum"
                :total="total">
        </el-pagination>
        <!--新增和修改弹窗-->
        <el-dialog
                title="提示"
                :visible.sync="dialogVisible"
                width="30%">
            <span>这是一段信息</span>
            <el-form ref="form" :model="news" label-width="80px">
                <el-form-item label="新闻标题">
                    <el-input v-model="news.title"></el-input>
                </el-form-item>
                <el-form-item label="新闻内容">
                    <el-input type="textarea" v-model="news.content"></el-input>
                </el-form-item>
                <el-form-item label="发布人">
                    <el-input v-model="news.publisher"></el-input>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="onSubmit(news)" icon="el-icon-plus">提交</el-button>
                    <el-button>取消</el-button>
                </el-form-item>
            </el-form>
        </el-dialog>
    </div>
</template>

<script>
  import cookie from "js-cookie"
    export default {
        name: "Main",
        data(){
            return{
                newsList:[],
                dialogVisible:false,//控制弹窗显示和隐藏
                news:{},
                pageSize:5,
                pageNum:1,
                total:0
            }
        },
        methods:{
            //分页查询
            getNewsPage(){
              this.$request.post("/news/list?pageNum="+this.pageNum+"&pageSize="+this.pageSize+"").then((response)=>{
                   console.log(response+"\t"+this.pageNum+"\t"+this.pageSize)
                    //将查询的数据放到newsList中
                    this.newsList=response.records
                    this.total=response.total;

                })
                    .catch(function (error) {
                        console.log("=====>"+error)
                    })
            },
            //当前页面改变重新查数据 点击上一页,下一页
            handPage(pageNum){
                this.pageNum=pageNum
                this.getNewsPage()
            },
            //处理新增
            newsAdd(){
                this.dialogVisible = true;
            },
            //新增新闻
            newsAdds(){
                this.$request.post('/news/add', this.news)
                    .then((response)=> {
                        if (response===true){
                            this.$message({
                                message: '新增成功',
                                type: 'success'
                            })

                        }
                    })
            },
            //查询要修改的数据,从前端的row中获取
            handleEdit($index,row){
                this.dialogVisible=true
                this.news = row
            },

            // 定义提交事件判断是新增还是修改
            onSubmit:function (news) {
                if (news.id!=null){
                    this.newsUpdate();
                    this.news={};

                }else {
                    this.newsAdds();
                    this.news={};
                    this.dialogVisible = false
                }
            },
            //修改方法
            newsUpdate(){
                this.$request.post('/news/update', this.news)
                    .then((response)=> {
                        if (response===true){
                            this.$message({
                                message: '修改成功',
                                type: 'success'
                            })
                            this.getNewsPage()
                        }
                    })
                //隐藏模态框
                this.dialogVisible={};
                this.dialogVisible = false

            },
            //删除操作
            newsDelete(id){
                this.$confirm('此操作将永久删除该文件, 是否继续?', '提示',{
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    this.$request.post("/news/delete?id="+id+"")
                        .then((res)=>{
                            if (res===true){
                                this.$message({
                                    message: '删除成功',
                                    type: 'success'
                                })
                                this.getNewsPage()
                            }else {
                                this.$message({
                                    message: '删除失败',
                                    type: 'success'
                                })
                            }
                        })
                }).catch(() => {
                    this.$message({
                        type: 'info',
                        message: '已取消删除'
                    });
                });
            }
        },
        mounted() {
            //默认时展示数据
            //this.getNewsList();
            //分页数据展示
            this.getNewsPage()
            //隐藏模态框
            this.dialogVisible=false;
        }
    }
</script>

<style scoped>

</style>

后端

导入依赖

    <dependencies>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>

        <!--druid 数据源 阿里巴巴提供的一款性能非常高的数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.8.0</version>
        </dependency>
    </dependencies>

application.properties

#配置数据源 druid  springboot默认的数据源 HikariDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/newsdb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=root
#指定数据源的类型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

#日志的实现类
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

#已删除的值
mybatis-plus.global-config.db-config.logic-delete-value=1
#未删除的值
mybatis-plus.global-config.db-config.logic-not-delete-value=0

mybatis-plus.mapper-locations=classpath:com/ho/mapper/*.xml

server.port=8081

解决跨域问题

package com.ho.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 *解决跨域问题
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")//允许所有路径访问
//                .allowCredentials(true)
                .allowedOrigins("*")//允许所有原始请求
                .allowedHeaders("*")//允许所有请求头
                .allowedMethods("GET","POST","DELETE","HEAD","PUT","OPTIONS","DELETE")//请求路径的方式
                .maxAge(3600)//设置过期时间
            // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
                .exposedHeaders("Server","Content-Length", "Authorization",
                "Access-Token",
                "Access-Control-Allow-Origin",
                "Access-Control-Allow-Credentials",
                        "token",
                        "Access-Control-Allow-Origin");
    }
}

配置分页插件

package com.ho.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class PageConfig {
    // 配置分页插件
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptorPage() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

配置 Spring Security

package com.ho.config;
import org.springframework.context.annotation.Configuration;
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;
/**
 * Spring Security
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/user/**").permitAll();//拦截的请求
        http.formLogin().loginPage("/user/login");//没有权限跳转页面
        http.csrf().disable();//关闭//csrf功能  登出失败的可能的原因
        http.rememberMe();
    }

}

dto下的R

package com.ho.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class R {
    @ApiModelProperty(value = "是否成功标示,true:成功;false:失败")
    private String success;
    @ApiModelProperty(value = "异常码")
    private String errorCode;
    @ApiModelProperty(value = "响应提示信息")
    private String msg;
    @ApiModelProperty(value = "返回数据")
    private Object data;

    //把构造方法私有
    private R(){}

    //返回集合List  return R.ok().data("list",list)
    //登录成功  return R.ok().message("登录成功");
    //登录失败(密码错误) return R.error().code(10000).message("密码错误")  //链式编程

    //成功静态方法
    public static R ok(){
        R r = new R();
        r.setSuccess("true");
        r.setErrorCode(ResultCode.SUCCESS);
        r.setMsg("成功");
        return r;
    }

    //失败静态方法
    public static R error(){
        R r = new R();
        r.setSuccess("false");
        r.setErrorCode(ResultCode.ERROR);
        r.setMsg("失败");
        return r;
    }

    public R success(String success){
        this.setSuccess(success);
        return this;
    }

    public R message(String message){
        this.setMsg(message);
        return this;
    }

    public R code(String code){
        this.setErrorCode(code);
        return this;
    }

    public  R data(Object data){
        this.setData(data);
        return this;
    }

}

dto下的ResultCode

package com.ho.dto;

public interface ResultCode {

    public static String SUCCESS = "00000";  //成功
    public static String ERROR = "20001";  //失败
}

newsController

package com.ho.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ho.pojo.News;
import com.ho.service.NewsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
//@CrossOrigin
public class NewsController {

    @Autowired
    private NewsService newsService;

    /**
     * 查询接口
     * @param pageNum
     * @param pageSize
     * @return
     */
    @RequestMapping("/news/list")
    @ResponseBody
    public Object list(@RequestParam(value = "pageNum",defaultValue = "1",required = false) Integer pageNum,
                       @RequestParam(value = "pageSize", defaultValue = "5", required = false) Integer pageSize) {


        Page<News> page = new Page<>(pageNum, pageSize);
        Page<News> userPage = newsService.page(page);
        return userPage;
    }

    /**
     * 新增
     * @param news
     * @return
     */
    @RequestMapping("/news/add")
    public Object NewsAdd(@RequestBody News news){
      return   newsService.save(news);
    }

    /**
     * 删除
     * @param id
     * @return
     */
    @RequestMapping("/news/delete")
    public Object delete(@RequestParam("id") Integer id){
        return newsService.removeById(id);
    }

    /**
     * 修改
     * @param news
     * @return
     */
    @RequestMapping("/news/update")
   public boolean update(@RequestBody News news) {
        QueryWrapper<News> queryWrapper = new QueryWrapper<News>();
        queryWrapper.eq("id",news.getId());
        return newsService.update(news,queryWrapper);
   }
}

UserController

package com.ho.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ho.dto.R;
import com.ho.dto.ResultCode;
import com.ho.pojo.Users;
import com.ho.service.UsersService;
import com.ho.utils.JWTUtil;
import com.ho.vo.TokenVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.*;

//@CrossOrigin
@RestController
public class UserController {

    @Autowired
    private UsersService usersService;

   @RequestMapping("/user/login")
   public String login(){
       return "login";
   }
    private static final long EXPRIVE_TIME=30*60*1000;

   @RequestMapping("/user/doLogin")
   @ResponseBody
    public Object doLogin(@RequestParam("username") String username, @RequestParam("password") String password,
                          HttpServletResponse response
    ) throws IOException {
       QueryWrapper<Users> queryWrapper = new QueryWrapper<Users>();
       queryWrapper.eq("u_user_name",username);
       queryWrapper.eq("password",password);


       Users users = usersService.getOne(queryWrapper);
//       if (users != null) {
//           String token = JWTUtil.sign(users.getUUserName(), users.getPassword());
//           response.setHeader("token",token);
//           return token;
//       }
       if (users != null) {
           String token = JWTUtil.sign(users.getUUserName(), String.valueOf(users.getId()));
           TokenVo tokenVo=TokenVo.builder()
                   .token(token)
                   .genTime(System.currentTimeMillis())
                   .expTime(System.currentTimeMillis()+EXPRIVE_TIME).build();

           R data = R.ok().data(tokenVo);
           //转json
           String tokenVoStr = new ObjectMapper().writeValueAsString(data);
           //设置编码格式
           response.setContentType("application/json;charset=utf-8");
           //通过response
           response.setHeader("token", token);
           PrintWriter out= new PrintWriter(new OutputStreamWriter(response.getOutputStream()));
           out.write(tokenVoStr);
           out.flush();
           out.close();
       }
       return ResultCode.ERROR;
    }
}

CheckTokenFilter

package com.ho.filer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ho.dto.R;
import com.ho.utils.JWTUtil;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Arrays;
import java.util.List;

@Component
public class CheckTokenFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        //设置不需要验证token的请求
        List<String> excludeUrl = Arrays.asList(
               "/user/login",
                "/user/doLogin"
        );
        //携带token进行跨域
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST");
        //res.setHeader("Access-Control-Allow-Headers", "Access-Control");
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Allow", "POST");
        String requestURI = request.getRequestURI();
        if(excludeUrl.contains(requestURI)){
            //放行
            filterChain.doFilter(request,response);
        }
        //验证token
        String token = request.getHeader("token");
        System.err.println("==="+token);
        if(!StringUtils.hasText(token)){
            response.setContentType("application/json;charset=utf-8");
            Writer out= new BufferedWriter(new OutputStreamWriter(response.getOutputStream()));
            String tokenEmpty = new ObjectMapper().writeValueAsString(R.error().message("token为空"));
            out.write(tokenEmpty);
            out.flush();
            out.close();
            return;
        }
        if(!JWTUtil.verify(token)){
            response.setContentType("application/json;charset=utf-8");
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.writeValue(response.getWriter(),
                    R.error().message("token不合法"));
            return;
        }
        //放行
        filterChain.doFilter(request,response);
    }
}

Mapper

package com.ho.service;
import com.ho.pojo.Users;
import com.baomidou.mybatisplus.extension.service.IService;
public interface UsersService extends IService<Users>{


}
package com.ho.service;
import com.ho.pojo.News;
import com.baomidou.mybatisplus.extension.service.IService;
public interface NewsService extends IService<News>{


}

Service

package com.ho.service;
import com.ho.pojo.News;
import com.baomidou.mybatisplus.extension.service.IService;
public interface NewsService extends IService<News>{


}
package com.ho.service;
import com.ho.pojo.Users;
import com.baomidou.mybatisplus.extension.service.IService;
public interface UsersService extends IService<Users>{


}
package com.ho.service.impl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ho.pojo.News;
import com.ho.mapper.NewsMapper;
import com.ho.service.NewsService;
@Service
public class NewsServiceImpl extends ServiceImpl<NewsMapper, News> implements NewsService{

}
package com.ho.service.impl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ho.pojo.Users;
import com.ho.mapper.UsersMapper;
import com.ho.service.UsersService;
@Service
public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements UsersService{

}

JWTUtils

package com.ho.utils;
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 java.io.UnsupportedEncodingException;
import java.util.Date;

/**
 * @author mo
 * @Description: Jwt工具类,用于生成和校验jwt
 */
public class JWTUtil {

    /**过期时间为半小时*/
    private static final long EXPRIE_TIME = 30*60*1000;//30分钟
    /**加密时的密钥,不能泄露*/
    private static final String secret = "B8B7EDFB-53AC-2BD2-E804-461A0C961F29";

    /**
     * 校验用户名和密码
     * @param token
     * @param username
     * @return
     */
    public static boolean verify(String token) {
        try {
            // 加密方式必须与生成token时的加密方式一致
            Algorithm algorithm = Algorithm.HMAC512(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .build();
            verifier.verify(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }


    /**
     * 获取token中的信息无需secret解密也能获得
     * @param token
     * @return
     */
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 获取token中的信息无需secret解密也能获得
     * @param token
     * @return
     */
    public static String getUserId(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("userId").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 生成JWTToken自定义 ItripUserDetails 用于封装用户信息
     * @param username
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String sign(String username,String userId) throws
            UnsupportedEncodingException {
        Date date = new Date(System.currentTimeMillis()+EXPRIE_TIME);
        Algorithm algorithm = Algorithm.HMAC512(secret);
        // 附带username信息
        return JWT.create()
                .withClaim("username", username)
                .withClaim("userId", userId)
                .withExpiresAt(date)
                .sign(algorithm);
    }
}

TokenVo

package com.ho.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

/**
 * @author mo
 * @Description: token响应对象
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "Token响应对象")
@Builder
public class TokenVo implements Serializable {

    private static final long serialVersionUID = 1959653550240034417L;

    @ApiModelProperty(value = "用户认证凭证")
    private String token;
    @ApiModelProperty(value = "过期时间,单位毫秒")
    private Long expTime;
    @ApiModelProperty(value = "生成时间,单位毫秒")
    private Long genTime;

}

pojo

/**
    * 新闻表
    */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "news")
public class News {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 新闻类型
     */
    @TableField(value = "type_id")
    private String typeId;

    /**
     * 新闻标题
     */
    @TableField(value = "title")
    private String title;

    /**
     * 发布者
     */
    @TableField(value = "publisher")
    private String publisher;

    /**
     * 发布者头像
     */
    @TableField(value = "publisher_face")
    private String publisherFace;

    /**
     * 新闻内容
     */
    @TableField(value = "content")
    private String content;

    /**
     * 发布时间
     */
    @TableField(value = "create_time")
    private Date createTime;

    /**
     * 修改时间
     */
    @TableField(value = "update_time")
    private Date updateTime;

    /**
     * 删除状态
     */
    @TableField(value = "deleted")
    private Integer deleted;

    @TableField(value = "typeId")
    private String typeid;

    public static final String COL_ID = "id";

    public static final String COL_TYPE_ID = "type_id";

    public static final String COL_TITLE = "title";

    public static final String COL_PUBLISHER = "publisher";

    public static final String COL_PUBLISHER_FACE = "publisher_face";

    public static final String COL_CONTENT = "content";

    public static final String COL_CREATE_TIME = "create_time";

    public static final String COL_UPDATE_TIME = "update_time";

    public static final String COL_DELETED = "deleted";

    public static final String COL_TYPEID = "typeId";
}

在SpringBoot3后台代码

Config

package com.yuan.spring_security.config;


import com.yuan.spring_security.filter.JwtAuthenticationFilter;
import com.yuan.spring_security.handler.CustomAccessDeniedHandler;
import com.yuan.spring_security.point.CustomAuthenticationEntryPoint;
import com.yuan.spring_security.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Autowired
    private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

    @Autowired
    private CustomAccessDeniedHandler customAccessDeniedHandler;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/auth/login", "/public").permitAll()
                        .anyRequest().authenticated()
                )
                .exceptionHandling(ex -> ex
                        .authenticationEntryPoint(customAuthenticationEntryPoint)
                        .accessDeniedHandler(customAccessDeniedHandler)
                )
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

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

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http, PasswordEncoder passwordEncoder) throws Exception {
        AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class);
        builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
        return builder.build();
    }
}

AuthController

package com.yuan.spring_security.controller;


import com.yuan.spring_security.domain.Users;
import com.yuan.spring_security.utils.JwtUtils;
import com.yuan.spring_security.utils.TokenBlacklist;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/auth")
@CrossOrigin
public class AuthController {

    @Autowired
    private TokenBlacklist tokenBlacklist;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtUtils jwtUtils;

    @PostMapping("/login")
    public Map<String, String> login(@RequestBody Users users) {
        Authentication auth = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(users.getUsername(), users.getPassword())
        );

        org.springframework.security.core.userdetails.User user = (org.springframework.security.core.userdetails.User) auth.getPrincipal();
        String token = jwtUtils.generateToken(user.getUsername(), user.getAuthorities().iterator().next().getAuthority());

        return Map.of("token", token);
    }

    @PostMapping("/logout")
    public Map<String, String> logout(@RequestHeader("Authorization") String authHeader) {
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring(7);
            tokenBlacklist.blacklistToken(token);
        }
        return Map.of("message", "Logged out successfully");
    }
}

DemoController

package com.yuan.spring_security.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@RestController
@RequestMapping("/api")
@CrossOrigin
public class DemoController {
    private List<String> testList = Arrays.asList("aaa", "bbb", "ccc");

    @GetMapping("/public")
    public String publicAccess() {
        return "公开接口,谁都能访问";
    }

    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/admin")
    public String adminOnly() {
        return "只有ADMIN角色能访问";
    }

    @PreAuthorize("hasRole('USER')")
    @GetMapping("/user")
    public String userOnly() {
        return "只有USER角色能访问";
    }

    @PreAuthorize("hasAnyRole('ADMIN','USER')")
    @GetMapping("/both")
    public String bothRoles() {
        return "ADMIN和USER都能访问";
    }


    @PreAuthorize("hasAnyRole('ADMIN','USER')")
    @GetMapping("/test")
    public String test() {
        Collections.shuffle(testList);
        System.out.println(testList.get(0));
        return testList.get(0);
    }
}

Users

package com.yuan.spring_security.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users implements Serializable {
    private Long id;
    private String username;
    private String password;
    private String role;

}

JwtAuthenticationFilter

package com.yuan.spring_security.filter;


import com.yuan.spring_security.service.MyUserDetailsService;
import com.yuan.spring_security.utils.JwtUtils;
import com.yuan.spring_security.utils.TokenBlacklist;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Autowired
    private TokenBlacklist tokenBlacklist;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);
            if (tokenBlacklist.isBlacklisted(token)) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }
            try {
                Claims claims = jwtUtils.parseToken(token);
                String username = claims.getSubject();

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authToken =
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

                SecurityContextHolder.getContext().setAuthentication(authToken);

                //自动续期
                if (jwtUtils.shouldRefresh(claims.getExpiration())) {
                    String role = (String) claims.get("role");
                    String newToken = jwtUtils.generateToken(username, role);
                    response.setHeader("X-Refresh-Token", newToken);
                }
            } catch (Exception e) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }
        }
        chain.doFilter(request, response);
    }
}

CustomAccessDeniedHandler

package com.yuan.spring_security.handler;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.yuan.spring_security.service.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);

        ApiResponse<?> result = ApiResponse.fail(403, "权限不足,无法访问此资源");
        response.getWriter().write(new ObjectMapper().writeValueAsString(result));
    }
}

UsersMapper

package com.yuan.spring_security.mapper;

import com.yuan.spring_security.domain.Users;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UsersMapper {
    Users findByUsername(@Param("username") String username);
}

CustomAuthenticationEntryPoint

package com.yuan.spring_security.point;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.yuan.spring_security.service.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

        ApiResponse<?> result = ApiResponse.fail(401, "用户名密码错误、请重试");
        response.getWriter().write(new ObjectMapper().writeValueAsString(result));
    }
}

ApiResponse

package com.yuan.spring_security.service;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;

    public static <T> ApiResponse<T> fail(int code, String message) {
        return new ApiResponse<>(code, message, null);
    }
}

MyUserDetailsService

package com.yuan.spring_security.service;


import com.yuan.spring_security.domain.Users;
import com.yuan.spring_security.mapper.UsersMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UsersMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Users user = userMapper.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        return org.springframework.security.core.userdetails.User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .roles(user.getRole().replace("ROLE_", ""))
                .build();
    }
}

JwtUtils

package com.yuan.spring_security.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.Date;

@Component
public class JwtUtils {

    private final String secretKey = "a8f7b36e5c9d4b12a1fef52d9c4a8e9d3f1b6a2c5d8e4f9a7b3c1d6e8f5a4c7d"; // 放配置文件更好
    private final long expirationMs = 86400000;   // 1天
    private static final long EXPIRATION = 60 * 60 * 1000L;         // 1小时
    private static final long REFRESH_THRESHOLD = 10 * 60 * 1000L;  // 剩余10分钟自动续期

    public String generateToken(String username, String role) {
        Date now = new Date();
        Date expiry = new Date(now.getTime() + expirationMs);

        return Jwts.builder()
                .setSubject(username)
                .claim("role", role)
                .setIssuedAt(now)
                .setExpiration(expiry)
                .signWith(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)), SignatureAlgorithm.HS256)
                .compact();
    }

    public Claims parseToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)))
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    public boolean shouldRefresh(Date expiration) {
        long remaining = expiration.getTime() - System.currentTimeMillis();
        return remaining < REFRESH_THRESHOLD;
    }
}

TokenBlacklist

package com.yuan.spring_security.utils;

import org.springframework.stereotype.Component;

import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class TokenBlacklist {

    private final Set<String> blacklist = ConcurrentHashMap.newKeySet();

    public void blacklistToken(String token) {
        blacklist.add(token);
    }

    public boolean isBlacklisted(String token) {
        return blacklist.contains(token);
    }
}

server:
  port: 8083

spring:
#  security:
#    user:
#      name: admin
#      password: 123456
  datasource:
    url: jdbc:mysql://localhost:3306/security_demo?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root

logging:
  level:
    web: info
    sql: debug


mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.yuan.spring_security.domain


xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yuan.spring_security.mapper.UsersMapper">


  <select id="findByUsername" resultType="com.yuan.spring_security.domain.Users">
    SELECT * FROM users WHERE username = #{username}
  </select>
</mapper>
avatar

yuanyourdomain

写代码,做研究,记录生活。

RECOMMENDED

MyBatis 动态 SQL

2026-07-08

Nginx 基础入门

2026-07-08

Maven 多模块与私服

2026-07-08