SpringMVC 文件上传
思路:
1, 首先定义页面, 定义多功能表单(enctype="multipart/form-data")
2, 在 Controller 里面定义一个方法, 用参数 (MultipartFile) 来接收前台传递过来的文件对象
3, 然后文件上传就是把文件从一个地方 (本地) 复制到另外一个地方(服务器)
添加 pom 依赖
- <dependency>
- <groupId>commons-fileupload</groupId>
- <artifactId>commons-fileupload</artifactId>
- <version>1.3.3</version>
- </dependency>
配置 springservlet-mvc.xml
- <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
- <!-- 必须和用户 JSP 的 pageEncoding 属性一致, 以便正确解析表单的内容 -->
- <property name="defaultEncoding" value="UTF-8"></property>
- <!-- 文件最大大小(字节) 1024*1024*50=50M-->
- <property name="maxUploadSize" value="52428800"></property>
- <!--resolveLazily 属性启用是为了推迟文件解析, 以便捕获文件大小异常 -->
- <property name="resolveLazily" value="true"/>
- </bean>
文件上传的方法
- /**
- * 文件上传的方法
- * @param req
- * @return
- */
- @RequestMapping("/upload")
- public String upload(HttpServletRequest req, MultipartFile xxx){
- String fileName = xxx.getOriginalFilename();
- String contentType = xxx.getContentType();
- try {
- FileUtils.copyInputStreamToFile(xxx.getInputStream(),new File("D:/xxx/"+fileName));
- } catch (IOException e) {
- e.printStackTrace();
- }
- return "redirect:/book/list";
- }
upload.jsp
- <%--
- Created by IntelliJ IDEA.
User: 源
- Date: 2019/10/30
- Time: 18:12
- To change this template use File | Settings | File Templates.
- --%>
- <%@ page contentType="text/html;charset=UTF-8" language="java" %>
- <HTML>
- <head>
- <title > 讲解 springmvc 文件上传</title>
- </head>
- <body>
- <form action="/book/upload" method="post" enctype="multipart/form-data">
- 请选择文件:<input type="file" name="xxx" />
- <input type="submit" value="ok" />
- </form>
- </body>
- </HTML>
上传结果
通过浏览器进行访问
Jwt 实现登录验证
思路:
登录界面向后台请求验证码, 后台就先调用随机函数生成验证码, 并且根据验证码生成一张图片, 以 base64 字符串的形式传到前台, 这时我们还要生成 verificationJwt 令牌做为请求验证码客户端的区分. 我们先将验证码信息存入 Redis.key 是 verificationJwt 令牌的值, value 就是验证码了. 并且将令牌放入到响应头. 传给客户端. 当客户端提交的时候将保持的 verificationJwt 令牌放入请求头带过来. 后端根据前端传过来的 jwt 令牌去 Redis 中获取数据, 将验证码拿到后和现有的验证码进行比较. 看看是否相等
细节:
访问一次登录页面, 生成一个 verificationJwt 令牌, 这个令牌的有效时间是 5min, 验证码在 Redis 中的有效时间是 1min, 无论是 verificationJwt 令牌超时失效, 还是验证码生成后超时失效, 都会造成登录失败;
简单来说就是跳转到登陆界面, 需要在 5min 中之内完成登录, 新的验证码出来后, 需要在 1min 中之内完成登录
导入 pom 依赖
- <!-- 引入 JWT 依赖, 由于是基于 Java, 所以需要的是 java-jwt -->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.1</version>
- </dependency>
- <dependency>
- <groupId>com.auth0</groupId>
- <artifactId>java-jwt</artifactId>
- <version>3.4.0</version>
- </dependency>
vue 前端代码
State.JS
- export default {
- resturantName: '天天餐馆',
- jwt:'',
- options: [],// 存放 tab 页的容器
- activeIndex: '',// 激活的 tab 页路由路径
- showName:'show',//tab 页的标题
- role:""// 用来区分是否是因为左侧菜单被点击造成的路由路径发生改变, 是: pass; 不是: nopass
- verificationJwt:null, // 这是用来保存用户等登录验证码 jwt 身份识别的
- }
Mutations.JS
- setVerificationJwt: (state, payload) => {
- state.verificationJwt = payload.verificationJwt;
- },
Getters.JS
- getVerificationJwt:(state) =>{
- return state.verificationJwt;
- },
action.JS
- /**
- * 对后台请求的地址的封装, URL 格式如下:
- * 模块名_实体名_操作
- */
- export default {
- // 'SERVER': 'http://localhost:8080/T216_SSH', // 服务器
- 'SERVER': 'http://localhost:8080', // /webDemo/ssm 服务器
- 'SYSTEM_USER_DOLOGIN': '/vue/user/login', // 用户登陆
- 'VERIFICATION': '/vue/user/verificationCode', // 用户登陆
- // 'SYSTEM_USER_DOLOGIN': '/vue/userAction_login.action', // 用户登陆
- // 'SYSTEM_USER_DOREG': '/vue/userAction_reg.action', // 用户注册
- 'SYSTEM_MENU_TREE': '/vue/treeNodeAction.action', // 左侧树形菜单加载
- 'SYSTEM_ARTICLE_LIST': '/vue/articleAction_list.action', // 文章列表
- 'SYSTEM_ARTICLE_ADD': '/vue/articleAction_add.action', // 文章新增
- 'SYSTEM_ARTICLE_EDIT': '/vue/articleAction_edit.action', // 文章修改
- 'SYSTEM_ARTICLE_DEL': '/vue/articleAction_del.action', // 文章删除
- 'SYSTEM_USER_GETASYNCDATA': '/vue/userAction_getAsyncData.action', //vuex 中的异步加载数据
- 'getFullPath': k => { // 获得请求的完整地址, 用于 mockjs 测试时使用
- return this.SERVER + this[k];
- }
- }
http.JS
- // 请求拦截器
- axios.interceptors.request.use(function(config) {
- // 设置验证码 jwt 令牌
- let verificationJwt = Windows.vm.$store.getters.getVerificationJwt;
- if (verificationJwt) {
- config.headers['verificationJwt'] = verificationJwt;
- }
- var jwt = Windows.vm.$store.getters.getJwt;
- config.headers['jwt'] = jwt;
- return config;
- }, function(error) {
- return Promise.reject(error);
- });
- // 响应拦截器
- axios.interceptors.response.use(function(response) {
- // debugger;
- // 保存验证码 jwt 令牌
- let verificationjwt = response.headers['verificationjwt'];
- if (verificationjwt) {
- Windows.vm.$store.commit('setVerificationJwt', {
- verificationJwt: verificationjwt
- });
- }
- var jwt = response.headers['jwt'];
- if (jwt) {
- Windows.vm.$store.commit('setJwt', {
- jwt: jwt
- });
- }
- return response;
- }, function(error) {
- return Promise.reject(error);
- });
登录界面 login.vue
- <template>
- <div class="login-wrap">
- <el-form class="login-container">
- <h1 class="title">用户登录</h1>
- <el-form-item label="">
- <el-input type="text" v-model="userName" placeholder="请输入登录账号" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="">
- <el-input type="password" v-model="userPwd" placeholder="请输入登录密码" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="">
- <el-row>
- <el-col :span="16">
- <el-input type="text" v-model="verificationCode" placeholder="请输入验证码" autocomplete="off"></el-input>
- </el-col>
- <el-col :span="8">
- <img id="img" :src="verificationCodeSrc" width="116px" height="40px" @click="changeVerificationCode">
- </el-col>
- </el-row>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" style="width: 100%;" @click="doSubmit">登 录</el-button>
- </el-form-item>
- <el-row style="text-align: center; margin-top: -15;">
- <el-link type="primary">忘记密码</el-link>
- <el-link type="primary" @click="gotoRegister">用户注册</el-link>
- </el-row>
- </el-form>
- </div>
- </template>
- <script>
- export default {
- name: 'Login',
- data: function() {
- return {
- userName: null,
- userPwd: null,
- verificationCode:null,
- verificationCodeSrc:null
- }
- },
- methods: {
- gotoRegister:function(){
- this.$router.push('/Register');
- },
- doSubmit: function() {
- let params = {
- uname: this.userName,
- pwd: this.userPwd,
- verificationCode: this.verificationCode
- };
- let url = this.axios.urls.SYSTEM_USER_DOLOGIN;
- this.axios.post(url, params).then(resp => {
- if(resp.data.status==200) {
- // 提示登录成功
- this.$message({
- message: resp.data.msg,
- type: 'success'
- });
- // 跳转路由
- this.$router.push({
- path:'/Main'
- })
- // 这是将用户信息保持下来
- // let user=resp.data.data
- // this.$store.dispatch('setUserAsync',{
- // user:user
- // });
- }else{
- this.$message({
- message: resp.data.msg,
- type: 'error'
- });
- }
- }).catch(resp => {
- this.$message({
- message: "请求异常",
- type: 'error'
- });
- });
- },
- // 更新验证码
- changeVerificationCode(){
- let url = this.axios.urls.VERIFICATION;
- this.axios.post(url, {}).then(resp => {
- this.verificationCodeSrc = resp.data;
- }).catch(resp => {
- console.log(resp);
- });
- }
- }
- ,
- created() {
- let url = this.axios.urls.VERIFICATION;
- this.axios.post(url, {}).then(resp => {
- this.verificationCodeSrc = resp.data;
- }).catch(resp => {
- console.log(resp);
- });
- }
- }
- </script>
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style scoped>
- .login-wrap {
- box-sizing: border-box;
- width: 100%;
- height: 100%;
- padding-top: 10%;
- background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjEzNjFweCIgaGVpZ2h0PSI2MDlweCIgdmlld0JveD0iMCAwIDEzNjEgNjA5IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCA0Ni4yICg0NDQ5NikgLSBodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2ggLS0+CiAgICA8dGl0bGU+R3JvdXAgMjE8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iQW50LURlc2lnbi1Qcm8tMy4wIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0i6LSm5oi35a+G56CB55m75b2VLeagoemqjCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTc5LjAwMDAwMCwgLTgyLjAwMDAwMCkiPgogICAgICAgICAgICA8ZyBpZD0iR3JvdXAtMjEiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDc3LjAwMDAwMCwgNzMuMDAwMDAwKSI+CiAgICAgICAgICAgICAgICA8ZyBpZD0iR3JvdXAtMTgiIG9wYWNpdHk9IjAuOCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzQuOTAxNDE2LCA1NjkuNjk5MTU4KSByb3RhdGUoLTcuMDAwMDAwKSB0cmFuc2xhdGUoLTc0LjkwMTQxNiwgLTU2OS42OTkxNTgpIHRyYW5zbGF0ZSg0LjkwMTQxNiwgNTI1LjE5OTE1OCkiPgogICAgICAgICAgICAgICAgICAgIDxlbGxpcHNlIGlkPSJPdmFsLTExIiBmaWxsPSIjQ0ZEQUU2IiBvcGFjaXR5PSIwLjI1IiBjeD0iNjMuNTc0ODc5MiIgY3k9IjMyLjQ2ODM2NyIgcng9IjIxLjc4MzA0NzkiIHJ5PSIyMS43NjYwMDgiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC0zIiBmaWxsPSIjQ0ZEQUU2IiBvcGFjaXR5PSIwLjU5OTk5OTk2NCIgY3g9IjUuOTg3NDY0NzkiIGN5PSIxMy44NjY4NjAxIiByeD0iNS4yMTczOTEzIiByeT0iNS4yMTMzMDk5NyI+PC9lbGxpcHNlPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0zOC4xMzU0NTE0LDg4LjM1MjAyMTUgQzQzLjg5ODQyMjcsODguMzUyMDIxNSA0OC41NzAyMzQsODMuNjgzODY0NyA0OC41NzAyMzQsNzcuOTI1NDAxNSBDNDguNTcwMjM0LDcyLjE2NjkzODMgNDMuODk4NDIyNyw2Ny40OTg3ODE2IDM4LjEzNTQ1MTQsNjcuNDk4NzgxNiBDMzIuMzcyNDgwMSw2Ny40OTg3ODE2IDI3LjcwMDY2ODgsNzIuMTY2OTM4MyAyNy43MDA2Njg4LDc3LjkyNTQwMTUgQzI3LjcwMDY2ODgsODMuNjgzODY0NyAzMi4zNzI0ODAxLDg4LjM1MjAyMTUgMzguMTM1NDUxNCw4OC4zNTIwMjE1IFoiIGlkPSJPdmFsLTMtQ29weSIgZmlsbD0iI0NGREFFNiIgb3BhY2l0eT0iMC40NSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik02NC4yNzc1NTgyLDMzLjE3MDQ5NjMgTDExOS4xODU4MzYsMTYuNTY1NDkxNSIgaWQ9IlBhdGgtMTIiIHN0cm9rZT0iI0NGREFFNiIgc3Ryb2tlLXdpZHRoPSIxLjczOTEzMDQzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNDIuMTQzMTcwOCwyNi41MDAyNjgxIEw3LjcxMTkwMTYyLDE0LjU2NDA3MDIiIGlkPSJQYXRoLTE2IiBzdHJva2U9IiNFMEI0QjciIHN0cm9rZS13aWR0aD0iMC43MDI2Nzg5NjQiIG9wYWNpdHk9IjAuNyIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtZGFzaGFycmF5PSIxLjQwNTM1Nzg5OTg3MzE1MywyLjEwODAzNjk1MzQ2OTk4MSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik02My45MjYyMTg3LDMzLjUyMTU2MSBMNDMuNjcyMTMyNiw2OS4zMjUwOTUxIiBpZD0iUGF0aC0xNSIgc3Ryb2tlPSIjQkFDQUQ5IiBzdHJva2Utd2lkdGg9IjAuNzAyNjc4OTY0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS1kYXNoYXJyYXk9IjEuNDA1MzU3ODk5ODczMTUzLDIuMTA4MDM2OTUzNDY5OTgxIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTE3IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMjYuODUwOTIyLCAxMy41NDM2NTQpIHJvdGF0ZSgzMC4wMDAwMDApIHRyYW5zbGF0ZSgtMTI2Ljg1MDkyMiwgLTEzLjU0MzY1NCkgdHJhbnNsYXRlKDExNy4yODU3MDUsIDQuMzgxODg5KSIgZmlsbD0iI0NGREFFNiI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxlbGxpcHNlIGlkPSJPdmFsLTQiIG9wYWNpdHk9IjAuNDUiIGN4PSI5LjEzNDgyNjUzIiBjeT0iOS4xMjc2ODA3NiIgcng9IjkuMTM0ODI2NTMiIHJ5PSI5LjEyNzY4MDc2Ij48L2VsbGlwc2U+CiAgICAgICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xOC4yNjk2NTMxLDE4LjI1NTM2MTUgQzE4LjI2OTY1MzEsMTMuMjE0MjgyNiAxNC4xNzk4NTE5LDkuMTI3NjgwNzYgOS4xMzQ4MjY1Myw5LjEyNzY4MDc2IEM0LjA4OTgwMTE0LDkuMTI3NjgwNzYgMCwxMy4yMTQyODI2IDAsMTguMjU1MzYxNSBMMTguMjY5NjUzMSwxOC4yNTUzNjE1IFoiIGlkPSJPdmFsLTQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDkuMTM0ODI3LCAxMy42OTE1MjEpIHNjYWxlKC0xLCAtMSkgdHJhbnNsYXRlKC05LjEzNDgyNywgLTEzLjY5MTUyMSkgIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTE0IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyMTYuMjk0NzAwLCAxMjMuNzI1NjAwKSByb3RhdGUoLTUuMDAwMDAwKSB0cmFuc2xhdGUoLTIxNi4yOTQ3MDAsIC0xMjMuNzI1NjAwKSB0cmFuc2xhdGUoMTA2LjI5NDcwMCwgMzUuMjI1NjAwKSI+CiAgICAgICAgICAgICAgICAgICAgPGVsbGlwc2UgaWQ9Ik92YWwtMiIgZmlsbD0iI0NGREFFNiIgb3BhY2l0eT0iMC4yNSIgY3g9IjI5LjExNzY0NzEiIGN5PSIyOS4xNDAyNDM5IiByeD0iMjkuMTE3NjQ3MSIgcnk9IjI5LjE0MDI0MzkiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC0yIiBmaWxsPSIjQ0ZEQUU2IiBvcGFjaXR5PSIwLjMiIGN4PSIyOS4xMTc2NDcxIiBjeT0iMjkuMTQwMjQzOSIgcng9IjIxLjU2ODYyNzUiIHJ5PSIyMS41ODUzNjU5Ij48L2VsbGlwc2U+CiAgICAgICAgICAgICAgICAgICAgPGVsbGlwc2UgaWQ9Ik92YWwtMi1Db3B5IiBzdHJva2U9IiNDRkRBRTYiIG9wYWNpdHk9IjAuNCIgY3g9IjE3OS4wMTk2MDgiIGN5PSIxMzguMTQ2MzQxIiByeD0iMjMuNzI1NDkwMiIgcnk9IjIzLjc0MzkwMjQiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC0yIiBmaWxsPSIjQkFDQUQ5IiBvcGFjaXR5PSIwLjUiIGN4PSIyOS4xMTc2NDcxIiBjeT0iMjkuMTQwMjQzOSIgcng9IjEwLjc4NDMxMzciIHJ5PSIxMC43OTI2ODI5Ij48L2VsbGlwc2U+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTI5LjExNzY0NzEsMzkuOTMyOTI2OCBMMjkuMTE3NjQ3MSwxOC4zNDc1NjEgQzIzLjE2MTYzNTEsMTguMzQ3NTYxIDE4LjMzMzMzMzMsMjMuMTc5NjA5NyAxOC4zMzMzMzMzLDI5LjE0MDI0MzkgQzE4LjMzMzMzMzMsMzUuMTAwODc4MSAyMy4xNjE2MzUxLDM5LjkzMjkyNjggMjkuMTE3NjQ3MSwzOS45MzI5MjY4IFoiIGlkPSJPdmFsLTIiIGZpbGw9IiNCQUNBRDkiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iR3JvdXAtOSIgb3BhY2l0eT0iMC40NSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTcyLjAwMDAwMCwgMTMxLjAwMDAwMCkiIGZpbGw9IiNFNkExQTYiPgogICAgICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC0yLUNvcHktMiIgY3g9IjcuMDE5NjA3ODQiIGN5PSI3LjE0NjM0MTQ2IiByeD0iNi40NzA1ODgyNCIgcnk9IjYuNDc1NjA5NzYiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTAuNTQ5MDE5NjA4LDEzLjYyMTk1MTIgQzQuMTIyNjI2ODEsMTMuNjIxOTUxMiA3LjAxOTYwNzg0LDEwLjcyMjcyMiA3LjAxOTYwNzg0LDcuMTQ2MzQxNDYgQzcuMDE5NjA3ODQsMy41Njk5NjA5NSA0LjEyMjYyNjgxLDAuNjcwNzMxNzA3IDAuNTQ5MDE5NjA4LDAuNjcwNzMxNzA3IEwwLjU0OTAxOTYwOCwxMy42MjE5NTEyIFoiIGlkPSJPdmFsLTItQ29weS0yIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzLjc4NDMxNCwgNy4xNDYzNDEpIHNjYWxlKC0xLCAxKSB0cmFuc2xhdGUoLTMuNzg0MzE0LCAtNy4xNDYzNDEpICI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC0xMCIgZmlsbD0iI0NGREFFNiIgY3g9IjIxOC4zODIzNTMiIGN5PSIxMzguNjg1OTc2IiByeD0iMS42MTc2NDcwNiIgcnk9IjEuNjE4OTAyNDQiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC0xMC1Db3B5LTIiIGZpbGw9IiNFMEI0QjciIG9wYWNpdHk9IjAuMzUiIGN4PSIxNzkuNTU4ODI0IiBjeT0iMTc1LjM4MTA5OCIgcng9IjEuNjE3NjQ3MDYiIHJ5PSIxLjYxODkwMjQ0Ij48L2VsbGlwc2U+CiAgICAgICAgICAgICAgICAgICAgPGVsbGlwc2UgaWQ9Ik92YWwtMTAtQ29weSIgZmlsbD0iI0UwQjRCNyIgb3BhY2l0eT0iMC4zNSIgY3g9IjE4MC4wOTgwMzkiIGN5PSIxMDIuNTMwNDg4IiByeD0iMi4xNTY4NjI3NSIgcnk9IjIuMTU4NTM2NTkiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMjguOTk4NTM4MSwyOS45NjcxNTk4IEwxNzEuMTUxMDE4LDEzMi44NzYwMjQiIGlkPSJQYXRoLTExIiBzdHJva2U9IiNDRkRBRTYiIG9wYWNpdHk9IjAuOCI+PC9wYXRoPgogICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTEwIiBvcGFjaXR5PSIwLjc5OTk5OTk1MiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTA1NC4xMDA2MzUsIDM2LjY1OTMxNykgcm90YXRlKC0xMS4wMDAwMDApIHRyYW5zbGF0ZSgtMTA1NC4xMDA2MzUsIC0zNi42NTkzMTcpIHRyYW5zbGF0ZSgxMDI2LjYwMDYzNSwgNC42NTkzMTcpIj4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC03IiBzdHJva2U9IiNDRkRBRTYiIHN0cm9rZS13aWR0aD0iMC45NDExNzY0NzEiIGN4PSI0My44MTM1NTkzIiBjeT0iMzIiIHJ4PSIxMS4xODY0NDA3IiByeT0iMTEuMjk0MTE3NiI+PC9lbGxpcHNlPgogICAgICAgICAgICAgICAgICAgIDxnIGlkPSJHcm91cC0xMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzQuNTk2Nzc0LCAyMy4xMTExMTEpIiBmaWxsPSIjQkFDQUQ5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPGVsbGlwc2UgaWQ9Ik92YWwtNyIgb3BhY2l0eT0iMC40NSIgY3g9IjkuMTg1MzQ3MTgiIGN5PSI4Ljg4ODg4ODg5IiByeD0iOC40NzQ1NzYyNyIgcnk9IjguNTU2MTQ5NzMiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTkuMTg1MzQ3MTgsMTcuNDQ1MDM4NiBDMTMuODY1NzI2NCwxNy40NDUwMzg2IDE3LjY1OTkyMzUsMTMuNjE0MzE5OSAxNy42NTk5MjM1LDguODg4ODg4ODkgQzE3LjY1OTkyMzUsNC4xNjM0NTc4NyAxMy44NjU3MjY0LDAuMzMyNzM5MTU2IDkuMTg1MzQ3MTgsMC4zMzI3MzkxNTYgTDkuMTg1MzQ3MTgsMTcuNDQ1MDM4NiBaIiBpZD0iT3ZhbC03Ij48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0zNC42NTk3Mzg1LDI0LjgwOTY5NCBMNS43MTY2NjA4NCw0Ljc2ODc4OTQ1IiBpZD0iUGF0aC0yIiBzdHJva2U9IiNDRkRBRTYiIHN0cm9rZS13aWR0aD0iMC45NDExNzY0NzEiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbCIgc3Ryb2tlPSIjQ0ZEQUU2IiBzdHJva2Utd2lkdGg9IjAuOTQxMTc2NDcxIiBjeD0iMy4yNjI3MTE4NiIgY3k9IjMuMjk0MTE3NjUiIHJ4PSIzLjI2MjcxMTg2IiByeT0iMy4yOTQxMTc2NSI+PC9lbGxpcHNlPgogICAgICAgICAgICAgICAgICAgIDxlbGxpcHNlIGlkPSJPdmFsLUNvcHkiIGZpbGw9IiNGN0UxQUQiIGN4PSIyLjc5NjYxMDE3IiBjeT0iNjEuMTc2NDcwNiIgcng9IjIuNzk2NjEwMTciIHJ5PSIyLjgyMzUyOTQxIj48L2VsbGlwc2U+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTM0LjYzMTI0NDMsMzkuMjkyMjcxMiBMNS4wNjM2NjY2Myw1OS43ODUwODIiIGlkPSJQYXRoLTEwIiBzdHJva2U9IiNDRkRBRTYiIHN0cm9rZS13aWR0aD0iMC45NDExNzY0NzEiPjwvcGF0aD4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDxnIGlkPSJHcm91cC0xOSIgb3BhY2l0eT0iMC4zMyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI4Mi41MzcyMTksIDQ0Ni41MDI4NjcpIHJvdGF0ZSgtMTAuMDAwMDAwKSB0cmFuc2xhdGUoLTEyODIuNTM3MjE5LCAtNDQ2LjUwMjg2NykgdHJhbnNsYXRlKDExNDIuNTM3MjE5LCAzMjcuNTAyODY3KSI+CiAgICAgICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTE3IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxNDEuMzMzNTM5LCAxMDQuNTAyNzQyKSByb3RhdGUoMjc1LjAwMDAwMCkgdHJhbnNsYXRlKC0xNDEuMzMzNTM5LCAtMTA0LjUwMjc0MikgdHJhbnNsYXRlKDEyOS4zMzM1MzksIDkyLjUwMjc0MikiIGZpbGw9IiNCQUNBRDkiPgogICAgICAgICAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJPdmFsLTQiIG9wYWNpdHk9IjAuNDUiIGN4PSIxMS42NjY2NjY3IiBjeT0iMTEuNjY2NjY2NyIgcj0iMTEuNjY2NjY2NyI+PC9jaXJjbGU+CiAgICAgICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMy4zMzMzMzMzLDIzLjMzMzMzMzMgQzIzLjMzMzMzMzMsMTYuODkwMDExMyAxOC4xMDk5ODg3LDExLjY2NjY2NjcgMTEuNjY2NjY2NywxMS42NjY2NjY3IEM1LjIyMzM0NDU5LDExLjY2NjY2NjcgMCwxNi44OTAwMTEzIDAsMjMuMzMzMzMzMyBMMjMuMzMzMzMzMywyMy4zMzMzMzMzIFoiIGlkPSJPdmFsLTQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDExLjY2NjY2NywgMTcuNTAwMDAwKSBzY2FsZSgtMSwgLTEpIHRyYW5zbGF0ZSgtMTEuNjY2NjY3LCAtMTcuNTAwMDAwKSAiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICAgICAgPGNpcmNsZSBpZD0iT3ZhbC01LUNvcHktNiIgZmlsbD0iI0NGREFFNiIgY3g9IjIwMS44MzMzMzMiIGN5PSI4Ny41IiByPSI1LjgzMzMzMzMzIj48L2NpcmNsZT4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTQzLjUsODguODEyNjY4NSBMMTU1LjA3MDUwMSwxNy42MDM4NTQ0IiBpZD0iUGF0aC0xNyIgc3Ryb2tlPSIjQkFDQUQ5IiBzdHJva2Utd2lkdGg9IjEuMTY2NjY2NjciPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTcuNSwzNy4zMzMzMzMzIEwxMjcuNDY2MjUyLDk3LjY0NDk3MzUiIGlkPSJQYXRoLTE4IiBzdHJva2U9IiNCQUNBRDkiIHN0cm9rZS13aWR0aD0iMS4xNjY2NjY2NyI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwb2x5bGluZSBpZD0iUGF0aC0xOSIgc3Ryb2tlPSIjQ0ZEQUU2IiBzdHJva2Utd2lkdGg9IjEuMTY2NjY2NjciIHBvaW50cz0iMTQzLjkwMjU5NyAxMjAuMzAyMjgxIDE3NC45MzU0NTUgMjMxLjU3MTM0MiAzOC41IDE0Ny41MTA4NDcgMTI2LjM2Njk0MSAxMTAuODMzMzMzIj48L3BvbHlsaW5lPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNTkuODMzMzMzLDk5Ljc0NTM4NDIgTDE5NS40MTY2NjcsODkuMjUiIGlkPSJQYXRoLTIwIiBzdHJva2U9IiNFMEI0QjciIHN0cm9rZS13aWR0aD0iMS4xNjY2NjY2NyIgb3BhY2l0eT0iMC42Ij48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTIwNS4zMzMzMzMsODIuMTM3MjEwNSBMMjM4LjcxOTQwNiwzNi4xNjY2NjY3IiBpZD0iUGF0aC0yNCIgc3Ryb2tlPSIjQkFDQUQ5IiBzdHJva2Utd2lkdGg9IjEuMTY2NjY2NjciPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMjY2LjcyMzQyNCwxMzIuMjMxOTg4IEwyMDcuMDgzMzMzLDkwLjQxNjY2NjciIGlkPSJQYXRoLTI1IiBzdHJva2U9IiNDRkRBRTYiIHN0cm9rZS13aWR0aD0iMS4xNjY2NjY2NyI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxjaXJjbGUgaWQ9Ik92YWwtNSIgZmlsbD0iI0MxRDFFMCIgY3g9IjE1Ni45MTY2NjciIGN5PSI4Ljc1IiByPSI4Ljc1Ij48L2NpcmNsZT4KICAgICAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJPdmFsLTUtQ29weS0zIiBmaWxsPSIjQzFEMUUwIiBjeD0iMzkuMDgzMzMzMyIgY3k9IjE0OC43NSIgcj0iNS4yNSI+PC9jaXJjbGU+CiAgICAgICAgICAgICAgICAgICAgPGNpcmNsZSBpZD0iT3ZhbC01LUNvcHktMiIgZmlsbC1vcGFjaXR5PSIwLjYiIGZpbGw9IiNEMURFRUQiIGN4PSI4Ljc1IiBjeT0iMzMuMjUiIHI9IjguNzUiPjwvY2lyY2xlPgogICAgICAgICAgICAgICAgICAgIDxjaXJjbGUgaWQ9Ik92YWwtNS1Db3B5LTQiIGZpbGwtb3BhY2l0eT0iMC42IiBmaWxsPSIjRDFERUVEIiBjeD0iMjQzLjgzMzMzMyIgY3k9IjMwLjMzMzMzMzMiIHI9IjUuODMzMzMzMzMiPjwvY2lyY2xlPgogICAgICAgICAgICAgICAgICAgIDxjaXJjbGUgaWQ9Ik92YWwtNS1Db3B5LTUiIGZpbGw9IiNFMEI0QjciIGN4PSIxNzUuNTgzMzMzIiBjeT0iMjMyLjc1IiByPSI1LjI1Ij48L2NpcmNsZT4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+);
- /* background-color: #112346; */
- background-repeat: no-repeat;
- background-position: center right;
- background-size: 100%;
- }
- .login-container {
- border-radius: 10px;
- margin: 0px auto;
- width: 350px;
- padding: 30px 35px 15px 35px;
- background: #fff;
- border: 1px solid #eaeaea;
- text-align: left;
- box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.1);
- }
- .title {
- margin: 0px auto 40px auto;
- text-align: center;
- color: #505458;
- }
- </style>
后端代码
Util 类
- CorsFilter
- package com.yuan.util;
- import java.io.IOException;
- import javax.servlet.Filter;
- import javax.servlet.FilterChain;
- import javax.servlet.FilterConfig;
- import javax.servlet.ServletException;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- /**
- * 配置 tomcat 允许跨域访问
- *
- * @author Administrator
- *
- */
- public class CorsFilter implements Filter {
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- }
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
- throws IOException, ServletException {
- HttpServletResponse resp = (HttpServletResponse) servletResponse;
- HttpServletRequest req = (HttpServletRequest) servletRequest;
- // Access-Control-Allow-Origin 就是我们需要设置的域名
- // Access-Control-Allow-Headers 跨域允许包含的头.
- // Access-Control-Allow-Methods 是允许的请求方式
- resp.setHeader("Access-Control-Allow-Origin", "*");//*, 任何域名
- resp.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");
- // resp.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With,
- // Content-Type, Accept");
- // 允许客户端, 发一个新的请求头 jwt
- // 允许客户端发送一个新的请求头
- resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, jwt, verificationJwt");
- // 允许客户端处理一个新的响应头 jwt
- resp.setHeader("Access-Control-Expose-Headers", "jwt");
- resp.setHeader("Access-Control-Expose-Headers", "verificationJwt");
- // String sss = resp.getHeader("Access-Control-Expose-Headers");
- // System.out.println("sss=" + sss);
- // 允许请求头 Token
- // httpResponse.setHeader("Access-Control-Allow-Headers","Origin,X-Requested-With,
- // Content-Type, Accept, Token");
- // System.out.println("Token=" + req.getHeader("Token"));
- if ("OPTIONS".equals(req.getMethod())) {// axios 的 Ajax 会发两次请求, 第一次提交方式为: option, 直接返回即可
- return;
- }
- filterChain.doFilter(servletRequest, servletResponse);
- }
- @Override
- public void destroy() {
- }
- }
- ImageUtil
- package com.yuan.util;
- import sun.misc.BASE64Encoder;
- import javax.imageio.ImageIO;
- import java.awt.*;
- import java.awt.image.BufferedImage;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.util.Random;
- public class ImageUtil {
- /**
- * 根据指定的随机数 生成验证码图片 转 base64
- * @param Word 要生存的验证码随机字符串
- * @param width 图片宽度
- * @param height 图片高度
- * @return base64 格式生成的验证码图片
- * @throws IOException
- */
- public static String createImageWithVerifyCode(String Word, int width, int height) throws IOException {
- String png_base64="";
- // 绘制内存中的图片
- BufferedImage bufferedImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
- // 得到画图对象
- Graphics graphics = bufferedImage.getGraphics();
- // 绘制图片前指定一个颜色
- graphics.setColor(getRandColor(160,200));
- graphics.fillRect(0,0,width,height);
- // 绘制边框
- graphics.setColor(Color.white);
- graphics.drawRect(0, 0, width - 1, height - 1);
- // 步骤四 四个随机数字
- Graphics2D graphics2d = (Graphics2D) graphics;
- graphics2d.setFont(new Font("宋体", Font.BOLD, 18));
- Random random = new Random();
- // 定义 x 坐标
- int x = 10;
- for (int i = 0; i <Word.length(); i++) {
- // 随机颜色
- graphics2d.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
- // 旋转 -30 --- 30 度
- int jiaodu = random.nextInt(60) - 30;
- // 换算弧度
- double theta = jiaodu * Math.PI / 180;
- // 获得字母数字
- char c = Word.charAt(i);
- // 将 c 输出到图片
- graphics2d.rotate(theta, x, 20);
- graphics2d.drawString(String.valueOf(c), x, 20);
- graphics2d.rotate(-theta, x, 20);
- x += 30;
- }
- // 绘制干扰线
- graphics.setColor(getRandColor(160, 200));
- int x1;
- int x2;
- int y1;
- int y2;
- for (int i = 0; i < 30; i++) {
- x1 = random.nextInt(width);
- x2 = random.nextInt(12);
- y1 = random.nextInt(height);
- y2 = random.nextInt(12);
- graphics.drawLine(x1, y1, x1 + x2, x2 + y2);
- }
- graphics.dispose();// 释放资源
- ByteArrayOutputStream baos = new ByteArrayOutputStream();//io 流
- ImageIO.write(bufferedImage, "png", baos);// 写入流中
- byte[] bytes = baos.toByteArray();// 转换成字节
- BASE64Encoder encoder = new BASE64Encoder();
- png_base64 = encoder.encodeBuffer(bytes).trim();
- png_base64 = png_base64.replaceAll("\n", "").replaceAll("\r","");// 删除 \r\n
- return png_base64;
- }
- /** 设置随机颜色 */
- private static Color getRandColor(int fc, int bc) {
- // 取其随机颜色
- Random random = new Random();
- if (fc> 255) {
- fc = 255;
- }
- if (bc> 255) {
- bc = 255;
- }
- int r = fc + random.nextInt(bc - fc);
- int g = fc + random.nextInt(bc - fc);
- int b = fc + random.nextInt(bc - fc);
- return new Color(r, g, b);
- }
- }
- JSONResult
- package com.yuan.util;
- public class JSONResult {
- // 响应业务状态
- private Integer status;
- // 响应消息
- private String msg;
- // 响应中的数据
- private Object data;
- private String ok; // 不使用
- public static JSONResult build(Integer status, String msg, Object data) {
- return new JSONResult(status, msg, data);
- }
- public static JSONResult ok(Object data) {
- return new JSONResult(data);
- }
- public static JSONResult ok() {
- return new JSONResult(null);
- }
- public static JSONResult errorMsg(String msg) {
- return new JSONResult(500, msg, null);
- }
- public static JSONResult errorMap(Object data) {
- return new JSONResult(501, "error", data);
- }
- public static JSONResult errorTokenMsg(String msg) {
- return new JSONResult(502, msg, null);
- }
- public static JSONResult errorException(String msg) {
- return new JSONResult(555, msg, null);
- }
- public JSONResult() {
- }
- public JSONResult(Integer status, String msg, Object data) {
- this.status = status;
- this.msg = msg;
- this.data = data;
- }
- public JSONResult(Object data) {
- this.status = 200;
- this.msg = "OK";
- this.data = data;
- }
- public Boolean isOK() {
- return this.status == 200;
- }
- public Integer getStatus() {
- return status;
- }
- public void setStatus(Integer status) {
- this.status = status;
- }
- public String getMsg() {
- return msg;
- }
- public void setMsg(String msg) {
- this.msg = msg;
- }
- public Object getData() {
- return data;
- }
- public void setData(Object data) {
- this.data = data;
- }
- public String getOk() {
- return ok;
- }
- public void setOk(String ok) {
- this.ok = ok;
- }
- }
- JwtUtils
- package com.yuan.util;
- import java.util.Date;
- import java.util.Map;
- import java.util.UUID;
- import javax.crypto.SecretKey;
- import javax.crypto.spec.SecretKeySpec;
- import org.apache.commons.codec.binary.Base64;
- import io.jsonwebtoken.Claims;
- import io.jsonwebtoken.JwtBuilder;
- import io.jsonwebtoken.Jwts;
- import io.jsonwebtoken.SignatureAlgorithm;
- /**
- * JWT 验证过滤器: 配置顺序 CorsFilte->JwtUtilsr-->StrutsPrepareAndExecuteFilter
- *
- */
- public class JwtUtils {
- /**
- * JWT_WEB_TTL:WEBAPP 应用中 token 的有效时间, 默认 30 分钟
- */
- public static final long JWT_WEB_TTL = 5 * 60 * 1000;
- /**
- * 将 jwt 令牌保存到 header 中的 key
- */
- public static final String JWT_HEADER_KEY = "jwt";
- public static final String JWT_VERIFICATION_KEY = "verificationJwt";
- // 指定签名的时候使用的签名算法, 也就是 header 那部分, jjwt 已经将这部分内容封装好了.
- private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
- private static final String JWT_SECRET = "f356cdce935c42328ad2001d7e9552a3";// JWT 密匙
- private static final SecretKey JWT_KEY;// 使用 JWT 密匙生成的加密 key
- // private static final SecretKey JWT_VERIFICATION_KEY;// 使用 JWT 密匙生成的加密 key
- static {
- byte[] encodedKey = Base64.decodeBase64(JWT_SECRET);
- JWT_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
- // 这里我偷个懒, 用户登录 jwt 密钥, 与图形验证码 jwt 密钥搞成同一个
- // JWT_VERIFICATION_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
- }
- private JwtUtils() {
- }
- /**
- * 解密 jwt, 获得所有声明(包括标准和私有声明)
- *
- * @param jwt
- * @return
- * @throws Exception
- */
- public static Claims parseJwt(String jwt) {
- Claims claims = Jwts.parser().setSigningKey(JWT_KEY).parseClaimsJws(jwt).getBody();
- return claims;
- }
- /**
- * 创建 JWT 令牌, 签发时间为当前时间
- *
- * @param claims
- * 创建 payload 的私有声明(根据特定的业务需要添加, 如果要拿这个做验证, 一般是需要和 jwt 的接收方提前沟通好验证方式的)
- * @param ttlMillis
- * JWT 的有效时间(单位毫秒), 当前时间 + 有效时间 = 过期时间
- * @return jwt 令牌
- */
- public static String createJwt(Map<String, Object> claims, long ttlMillis) {
- // 生成 JWT 的时间, 即签发时间
- long nowMillis = System.currentTimeMillis();
- // 下面就是在为 payload 添加各种标准声明和私有声明了
- // 这里其实就是 new 一个 JwtBuilder, 设置 jwt 的 body
- JwtBuilder builder = Jwts.builder()
- // 如果有私有声明, 一定要先设置这个自己创建的私有的声明, 这个是给 builder 的 claim 赋值, 一旦写在标准的声明赋值之后, 就是覆盖了那些标准的声明的
- .setClaims(claims)
- // 设置 jti(JWT ID): 是 JWT 的唯一标识, 根据业务需要, 这个可以设置为一个不重复的值, 主要用来作为一次性 token, 从而回避重放攻击.
- // 可以在未登陆前作为身份标识使用
- .setId(UUID.randomUUID().toString().replace("-", ""))
- // iss(Issuser)签发者, 写死
- // .setIssuer("zking")
- // iat: jwt 的签发时间
- .setIssuedAt(new Date(nowMillis))
- // 代表这个 JWT 的主体, 即它的所有人, 这个是一个 JSON 格式的字符串, 可放数据{"uid":"zs"}. 此处没放
- // .setSubject("{}")
- // 设置签名使用的签名算法和签名使用的秘钥
- .signWith(SIGNATURE_ALGORITHM, JWT_KEY)
- // 设置 JWT 的过期时间
- .setExpiration(new Date(nowMillis + ttlMillis));
- return builder.compact();
- }
- /**
- * 复制 jwt, 并重新设置签发时间 (为当前时间) 和失效时间
- *
- * @param jwt
- * 被复制的 jwt 令牌
- * @param ttlMillis
- * jwt 的有效时间(单位毫秒), 当前时间 + 有效时间 = 过期时间
- * @return
- */
- public static String copyJwt(String jwt, Long ttlMillis) {
- Claims claims = parseJwt(jwt);
- // 生成 JWT 的时间, 即签发时间
- long nowMillis = System.currentTimeMillis();
- // 下面就是在为 payload 添加各种标准声明和私有声明了
- // 这里其实就是 new 一个 JwtBuilder, 设置 jwt 的 body
- JwtBuilder builder = Jwts.builder()
- // 如果有私有声明, 一定要先设置这个自己创建的私有的声明, 这个是给 builder 的 claim 赋值, 一旦写在标准的声明赋值之后, 就是覆盖了那些标准的声明的
- .setClaims(claims)
- // 设置 jti(JWT ID): 是 JWT 的唯一标识, 根据业务需要, 这个可以设置为一个不重复的值, 主要用来作为一次性 token, 从而回避重放攻击.
- // 可以在未登陆前作为身份标识使用
- //.setId(UUID.randomUUID().toString().replace("-", ""))
- // iss(Issuser)签发者, 写死
- // .setIssuer("zking")
- // iat: jwt 的签发时间
- .setIssuedAt(new Date(nowMillis))
- // 代表这个 JWT 的主体, 即它的所有人, 这个是一个 JSON 格式的字符串, 可放数据{"uid":"zs"}. 此处没放
- // .setSubject("{}")
- // 设置签名使用的签名算法和签名使用的秘钥
- .signWith(SIGNATURE_ALGORITHM, JWT_KEY)
- // 设置 JWT 的过期时间
- .setExpiration(new Date(nowMillis + ttlMillis));
- return builder.compact();
- }
- public static Claims validateJwtToken(String jwt) {
- Claims claims = null;
- try {
- if (null != jwt) {
- claims = JwtUtils.parseJwt(jwt);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- return claims;
- }
- }
- VerifyCodeUtil
- package com.yuan.util;
- import java.util.Random;
- public class VerifyCodeUtil {
- /** 生成 N 位数字和字母混合的验证码
- * @param num 验证码位数
- * @return code 生成的验证码字符串 */
- public static String produceNumAndChar(int num){
- Random random = new Random();
- String code = "";
- String ch = "ABCDEFGHIJKLMNPQRSTUVWXYZ";
- String n = "123456789";
- for(int i=0;i<num;i++){
- int flag = random.nextInt(2);
- if(flag==0){// 数字
- code+=n.charAt(random.nextInt(n.length()));
- }else{// 字母
- code+=ch.charAt(random.nextInt(ch.length()));
- }
- }
- return code;
- }
- }
实体类 User
- package com.yuan.model;
- public class User {
- private String uname;
- private String pwd;
- public User(String uname, String pwd) {
- this.uname = uname;
- this.pwd = pwd;
- }
- public User() {
- super();
- }
- public String getUname() {
- return uname;
- }
- public void setUname(String uname) {
- this.uname = uname;
- }
- public String getPwd() {
- return pwd;
- }
- public void setPwd(String pwd) {
- this.pwd = pwd;
- }
- }
UserMapper.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.mapper.UserMapper">
- <select id="login" parameterType="java.lang.String">
- select
- *
- from t_vue_user
- where uname = #{uname,jdbcType=VARCHAR}
- </select>
- </mapper>
- UserMapper
- package com.yuan.mapper;
- import com.yuan.model.User;
- import org.springframework.stereotype.Repository;
- @Repository
- public interface UserMapper {
- User login(User user);
- }
- UserService
- package com.yuan.service;
- import com.yuan.model.User;
- public interface UserService {
- public User login(User user);
- }
- UserServiceImpl
- package com.yuan.service.impl;
- import com.yuan.mapper.UserMapper;
- import com.yuan.model.User;
- import com.yuan.service.UserService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- @Service
- public class UserServiceImpl implements UserService {
- @Autowired
- private UserMapper userMapper;
- @Override
- public User login(User user) {
- return userMapper.login(user);
- }
- }
- UserController
- package com.yuan.controller;
- import com.yuan.model.User;
- import com.yuan.service.UserService;
- import com.yuan.util.ImageUtil;
- import com.yuan.util.JSONResult;
- import com.yuan.util.JwtUtils;
- import com.yuan.util.VerifyCodeUtil;
- import io.jsonwebtoken.Claims;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.Redis.core.RedisTemplate;
- import org.springframework.stereotype.Controller;
- import org.springframework.util.StringUtils;
- import org.springframework.Web.bind.annotation.RequestMapping;
- import org.springframework.Web.bind.annotation.ResponseBody;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.concurrent.TimeUnit;
- @Controller
- @RequestMapping("/vue/user")
- public class UserController {
- private static final String VERIFICATION_CODE = "verificationCode_";
- @Autowired
- private UserService userService;
- @Autowired
- private RedisTemplate redisTemplate;
- @RequestMapping("/login")
- @ResponseBody
- public JSONResult login(User u, HttpServletRequest request, HttpServletResponse response){
- // 获取用户输入的验证码
- String userVerificationCode = request.getParameter("verificationCode");
- // 获取验证码 jwt 令牌
- String userJwt = request.getHeader(JwtUtils.JWT_VERIFICATION_KEY);
- // 获取到保存在 Redis 中的验证码
- Object redisVerificationCode = redisTemplate.opsForValue().get(VERIFICATION_CODE + userJwt) ;
- // 这里存在两种情况: 1, 令牌超时 2, 验证码超时
- if(StringUtils.isEmpty(redisVerificationCode)){
- return JSONResult.errorMsg("你的验证码已超时");
- }
- if(!redisVerificationCode.toString().equalsIgnoreCase(userVerificationCode)){
- return JSONResult.errorMsg("验证码错误");
- }
- User user = userService.login(u);
- // 判断是否登录成功
- if(user != null){
- Map<String,Object> map=new HashMap<String, Object>();
- map.put("User", user);
- // 这是颁发用户登录成功的 jwt 令牌
- String jwt= JwtUtils.createJwt(map, JwtUtils.JWT_WEB_TTL);
- response.setHeader(JwtUtils.JWT_HEADER_KEY, jwt);
- return JSONResult.ok(user);
- }else {
- return JSONResult.errorMsg("密码或账户错误");
- }
- }
- /** 生成图片验证码 */
- @RequestMapping("/verificationCode")
- @ResponseBody
- public String verificationCode(HttpServletRequest req, HttpServletResponse resp) throws IOException {
- // 生成验证码随机数
- String Word = VerifyCodeUtil.produceNumAndChar(4);
- // 获取用户的 jwt 令牌
- String userVerificationJwt = req.getHeader(JwtUtils.JWT_VERIFICATION_KEY);
- // 验证码令牌
- Claims claims = JwtUtils.validateJwtToken(userVerificationJwt);
- if(claims == null){
- // 如果用户令牌过期那么对应存放在 Redis 中的数据也要清空
- if(!StringUtils.isEmpty(userVerificationJwt)){
- redisTemplate.expire(VERIFICATION_CODE + userVerificationJwt, 1, TimeUnit.DAYS);
- }
- userVerificationJwt = JwtUtils.createJwt(new HashMap<String, Object>() ,JwtUtils.JWT_WEB_TTL);
- // 将 jwt 令牌放入 response head 中
- resp.setHeader(JwtUtils.JWT_VERIFICATION_KEY, userVerificationJwt);
- }
- // 刷新缓存, 更新验证码
- redisTemplate.opsForValue().set(VERIFICATION_CODE + userVerificationJwt , Word,60, TimeUnit.SECONDS);
- // 生成图片
- String code = "data:image/png;base64," + ImageUtil.createImageWithVerifyCode(Word, 116,40);;
- return code;
- }
- }
Web.xml
- <Web-App xmlns="http://xmlns.jcp.org/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
- version="3.1">
- <display-name>Archetype Created Web Application</display-name>
- <welcome-file-list>
- <welcome-file>login.jsp</welcome-file>
- </welcome-file-list>
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:applicationContext.xml</param-value>
- </context-param>
- <!-- 读取 Spring 上下文的监听器 -->
- <listener>
- <listener-class>org.springframework.Web.context.ContextLoaderListener</listener-class>
- </listener>
- <!-- Spring 和 web 项目集成 end -->
- <!-- 防止 Spring 内存溢出监听器 -->
- <listener>
- <listener-class>org.springframework.Web.util.IntrospectorCleanupListener</listener-class>
- </listener>
- <!-- 中文乱码处理 -->
- <filter>
- <filter-name>encodingFilter</filter-name>
- <filter-class>org.springframework.Web.filter.CharacterEncodingFilter</filter-class>
- <!--web.xml 3.0 的新特性, 是否支持异步 -->
- <async-supported>true</async-supported>
- <init-param>
- <param-name>encoding</param-name>
- <param-value>UTF-8</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>encodingFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <!-- 解决 cors 跨域问题过滤器 -->
- <filter>
- <filter-name>corsFilter</filter-name>
- <filter-class>com.yuan.util.CorsFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>corsFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <!-- Spring MVC servlet -->
- <servlet>
- <servlet-name>SpringMVC</servlet-name>
- <servlet-class>org.springframework.Web.servlet.DispatcherServlet</servlet-class>
- <!-- 此参数可以不配置, 默认值为:/WEB-INF/springmvc-servlet.xml-->
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>/Web-INF/springmvc-servlet.xml</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- <!--web.xml 3.0 的新特性, 是否支持异步 -->
- <async-supported>true</async-supported>
- </servlet>
- <servlet-mapping>
- <servlet-name>SpringMVC</servlet-name>
- <url-pattern>/</url-pattern>
- </servlet-mapping>
- </Web-App>
最后看一下运行结果
谢谢观看!!!
来源: http://www.bubuko.com/infodetail-3268523.html