今日计划写前端
首先搭建vue2.0环境,具体百度 编写一个简单的login页面和index页面
login.vue
<template>
<body id="paper">
<el-form :model="loginForm" class="login-container" label-position="left"
label-width="0px" v-loading="loading" size="medium">
<h3 class="login_title">系统登录</h3>
<el-form-item prop="username">
<el-input type="text" v-model="loginForm.username"
auto-complete="off" placeholder="账号"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" v-model="loginForm.password"
auto-complete="off" placeholder="密码"></el-input>
</el-form-item>
<el-checkbox class="login_remember" v-model="loginForm.checked"
label-position="left">
<span style="color: #505458">自动登录</span>
</el-checkbox>
<el-form-item style="width: 100%">
<el-button type="primary" style="width: 100%;border: none;box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)"
@click="toLogin" round>登录</el-button>
</el-form-item>
</el-form>
</body>
</template>
<script>
import { getCompleteWords ,login } from "@/request/userApi";
let api ={}
api.getCompleteWords = getCompleteWords
api.login = login
export default {
name:'Login',
data(){
return {
loading:false,
loginForm:{
username:'',
password:'',
checked:true,
}
}
},
methods: {
CompleteWords(){
let data = {
user_id:25,
query : "",
}
getCompleteWords(data).then(res => {
console.log(res);
})
},
toLogin(){
let _this = this
let data = {
username:_this.loginForm.username,
password:_this.loginForm.password
}
api.login(data).then(res => {
console.log(res)
})
}
},
mounted() {
},
}
</script>
<style>
#paper {
background:url("../assets/img/bg/eva1.jpg") no-repeat;
background-position: center;
height: 100%;
width: 100%;
background-size: cover;
position: fixed;
}
body{
margin: 0;
}
.login-container {
border-radius: 15px;
background-clip: padding-box;
margin: 90px auto;
width: 350px;
padding: 35px 35px 15px 35px;
background: #fff;
border: 1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
}
.login_title {
margin: 0px auto 40px auto;
text-align: center;
color: #505458;
}
.login_remember {
margin: 0px 0px 35px 0px;
text-align: left;
}
</style>
index.vue
<template>
<div>
helloworld
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
配置路由
const router = new Router({
routes: [
{
path: "/",
name: "Index",
meta: {
requireAuth: true
},
component: Index
},
{
path: "/login",
name: "Login",
component: Login
}
]
});
编写路由全局守卫
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) {
//判断该路由是否需要登陆权限
if (store.state.token) {
//通过vuex state获取当前的token是否存在
next();
} else {
next({
path: "/login",
query: { redirect: to.fullPath } // 将跳转的路由path作为参数,登录成功后跳转到该路由
});
}
} else {
next();
}
});
封装axios
import axios from "axios";
import store from "../store/main";
// 让请求在浏览器中允许跨域携带cookie
axios.defaults.withCredentials = true;
// 使用自定义配置新建一个 axios 实例
const service = axios.create({
// 基础的请求地址
baseURL: "/api",
// 设置超时时间 1s
timeout: 1000
});
// request拦截器
service.interceptors.request.use(
config => {
// let url = config.url.replace(config.baseURL, "");
// let code = config.code;
// config.headers = getHeader(url, code); // 让每个请求携带自定义签名
if (store.state.token) {
// 判断是否存在token,如果存在的话,则每个http header都加上token
config.headers.Authorization = `token ${store.state.token}`;
}
return config;
},
error => {
console.log(error); // for debug 11
Promise.reject(error);
}
);
// http response 拦截器
service.interceptors.response.use(
response => {
return response;
},
error => {
if (error.response) {
switch (error.response.status) {
case 401:
// 返回 401 清除token信息并跳转到登录页面(未授权返回)
// store.commit(types.LOGOUT);
// router.replace({
// path: "login",
// query: { redirect: router.currentRoute.fullPath }
// });
}
}
return Promise.reject(error.response.data); // 返回接口返回的错误信息
}
);
export default service;
因为后端思想,决定把所有请求写在几个接口文件之中 结构图:
axios请求详情
const login = data => {
return request({
// 表单数据加这个请求头
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
url: "admin/user/login",
method: "post",
data: qs.stringify(data) //用 qs 将js对象转换为字符串
});
};
然后将api 暴露出去
var userApi = {
login
};
export default userApi;
在页面中引入只需要
import userApi from "@/request/userApi";
使用
toLogin(){
let _this = this
let data = {
username:_this.loginForm.username,
password:_this.loginForm.password
}
userApi.login(data).then(res => {
console.log(res)
})
}
接下来对密码进行md5加密
引入md5包
npm install --save js-md5
在main.js中将md5加入Vue原型方便调用
// 引入md5加密
import md5 from "js-md5";
// 把md5放入vue的原型方便调用
Vue.prototype.$MD5 = md5;
发送请求的时候加密
let data = {
username:_this.loginForm.username,
password:_this.$MD5(_this.loginForm.password),
}
其中遇到的问题
- 后端接受格式是表单格式,但是axios默认发的是json格式
后端接收参数 如果这样发送的话会报错
Required request parameter ‘xxx‘ for method parameter type xxx is not present
因此需要在前端修改请求头类型 如果后端接受的是表单类型,即用@RequestParam注解的多个参数 修改请求文件 模板:
// form请求格式;
// return request({
// // 表单数据加这个请求头
// headers: {
// "Content-Type": "application/x-www-form-urlencoded"
// },
// url: "admin/user/login",
// method: "post",
// data: qs.stringify(data) //用 qs 将js对象转换为字符串
// });
如果接受的是json对象,即用@RequestBody注解的 直接发送
// json请求格式
// return request({
// url: "admin/user/login",
// method: "post",
// data: data
// });
开始准备完善后端的登录逻辑
先整合一个日志框架
打算用logback 因为在我编写这篇日记的前几天刚刚被爆出来log4j出现巨大漏洞,虽然说马上出了补丁, 但是logback据说比log4j更好,所以打算用这个
其实在之前我一直都是直接使用sout的,主要是因为懒 System.out.print的优点:直观、方便。 Log的优点:异步、解耦、灵活、策略多。 但是sout除了方便没有任何优点,所以整合一个logback
一般情况下不用导包,因为springboot自带Logback
在src/resources文件夹下创建logback-spring.xml 如果要复用这个logback.xml文件,请搜索springprofile,将里面的值改成自己项目的
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="10 seconds">
<contextName>logback</contextName>
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
<springProfile name="dev">
<property name="log.path" value="F:\\leezed\\project\\logs\\SpringbootVueMhtLogs" />
</springProfile>
<springProfile name="online">
<!--linux下路径,到时候再写-->
<property name="log.path" value="F:\\leezed\\project\\logs\\SpringbootVueMhtLogs" />
</springProfile>
<!-- 彩色日志 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!--输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--输出到文件-->
<!-- 时间滚动输出 level为 DEBUG 日志 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_debug.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志归档 -->
<fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录debug级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 INFO 日志 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_info.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录info级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 WARN 日志 -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_warn.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录warn级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 ERROR 日志 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_error.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--
<logger>用来设置某一个包或者具体的某一个类的日志打印级别、
以及指定<appender>。<logger>仅有一个name属性,
一个可选的level和一个可选的addtivity属性。
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
如果未设置此属性,那么当前logger将会继承上级的级别。
addtivity:是否向上级logger传递打印信息。默认是true。
-->
<!--<logger name="org.springframework.web" level="info"/>-->
<!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
<!--
使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
-->
<!--
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
不能设置为INHERITED或者同义词NULL。默认是DEBUG
可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-->
<!--开发环境:打印控制台-->
<springProfile name="dev">
<logger name="com.lee.mht.system.dao" level="debug"/>
<logger name="com.lee.mht.business.dao" level="debug"/>
</springProfile>
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="DEBUG_FILE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="WARN_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
<!--暂时还没上线就没有写上线后要打印的内容-->
<springProfile name="online">
</springProfile>
</configuration>
使用logback
在成功引入logback后 只要在类中加入这个变量
private final Logger logger = LoggerFactory.getLogger(当前类名.class);
然后再方法中使logger.info(“xxx”),既可以输出日志了,但是人就是懒 幸好有万能的lombok 只要在类上加上 @Slf4j注解 直接就可以在方法中使用log.info(“xxx”)了
这样logback就完成了
shiro的学习
转载自https://blog.csdn.net/sqlgao22/article/details/98506479 原文链接
shiro是目前主流的java安全框架,主要用来更便捷的认证,授权,加密,会话管理。 验证的过程是:
- 创建SecurityManager安全管理器;
- Subject主体带授权信息执行授权,请求到SecurityManager
- SecurityManager安全管理器调用Authorizer授权
- Authorizer结合主体一步步传过来的授权信息与Realm中的数据比对.
subject(用户)调用SecurityManager进行权限验证,然后SecurityManager根据Realm的规则仅限用户的身份和权限的比对.
shiro 架构
shiro的3个主要的组件:
-
Subject 主体,当前参与应用安全部分的主体。可以是用户,可以是第三方服务,可以是cron 任务,或者任何东西。主要指一个正在与当前软件交互的东西。所有Subject都需要SecurityManager,当与Subject进行交互,这些交互行为实际上被转换为与SecurityManager的交互 -
SecurityManager 安全管理员,Shiro架构的核心,它就像Shiro内部所有原件的保护伞。然而一旦配置了SecurityManager,SecurityManager就用到的比较少,开发者大部分时间都花在Subject上面。当你与Subject进行交互的时候,实际上是SecurityManager在背后帮你举起Subject来做一些安全操作。 -
Realms Realms作为Shiro和应用的连接桥,当需要与安全数据交互的时候,像用户账户,或者访问控制,Shiro就从一个或多个Realms中查找。Shiro提供了一些可以直接使用的Realms,如果默认的Realms不能满足你的需求,你也可以定制自己的Realms
之后的内容我就不转载了
开始使用
- 引入shiro
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
- 自定义realm规则
Realm中有3种验证的方式,
- InitRealm
- JdbcRealm.
- 自定义的Realm.
通常使用自定义的方式. 使用自定义的Realm继承AuthorizingRealm,并重写两个方法,一个是验证身份,一个是验证权限
初步编写realm
/**
* @author FucXing
* @date 2021/12/23 12:53
**/
@Slf4j
public class MyRealm extends AuthorizingRealm {
@Autowired(required = false)
private AdminUserDao adminUserDao;
//用户授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//用户登入
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("开始用户验证");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
String password = adminUserDao.getpasswordByUsername(username);
if(password == null || !password.equals(new String(token.getPassword()))){
throw new AuthenticationException("用户名或密码错误");
}
//组合一个验证信息
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(),password,getName());
return info;
}
}
编写ShiroConfig
@Configuration
public class ShiroConfig {
/**
* 创建自定义的验证规则
*/
@Bean
public Realm myRealm() {
return new MyRealm();
}
/**
* 创建安全管理
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm());
return manager;
}
//配置权限过滤器,以下内容是抄的,先把框架搭出来,到时候再改
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//SecurityManager 安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//登录,为后台接口名,非前台页面名, 未登录跳转到这里
shiroFilterFactoryBean.setLoginUrl("/pub/login");
//登录成功后跳转的地址,为后台接口名,非前台页面名
shiroFilterFactoryBean.setSuccessUrl("/api/index");
//无权限跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/pub/unauthorized");
// 过滤链定义,从上向下顺序执行,必须保证是有序的,所以用linked
LinkedHashMap<String, String> filterMap = new LinkedHashMap<String, String>();
//公开的接口,都能访问
filterMap.put("/", "anon");
filterMap.put("/pub/**", "anon");
//api下的接口需要认证后才能访问
filterMap.put("/api/**", "autch");
//roles表示需要特定的角色才能访问
filterMap.put("/user/**", "roles[user]");
filterMap.put("/admin/**", "roles[admin]");
filterMap.put("/root/**", "roles[root]");
//防止有忘记写的接口,剩下的都需要认证才能访问
filterMap.put("/**", "autch");
//logout是shiro提供的过滤器
filterMap.put("/logout", "logout");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
}
其中遇到的问题
在SecrityManger中会报错
Error:(27, 16) java: 不兼容的类型: org.apache.shiro.web.mgt.DefaultWebSecurityManager无法转换为java.lang.SecurityManager
需要手动导入import org.apache.shiro.mgt.SecurityManager;
又出了个大问题
报错:for example: not eligible for auto-proxying
2021-12-23 19:37:07.139 INFO 18940 --- [ restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'shiroConfig' of type [com.lee.mht.system.config.ShiroConfig$$EnhancerBySpringCGLIB$$e6711d91] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-12-23 19:37:07.255 INFO 18940 --- [ restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'myRealm' of type [com.lee.mht.system.config.LeeRealm] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-12-23 19:37:07.259 INFO 18940 --- [ restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'securityManager' of type [org.apache.shiro.web.mgt.DefaultWebSecurityManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
经过查阅后发现这个问题时因为shiroconfig作为configuration 在springboot加载顺序顺序比beanfactory先,导致上面报错的几个bean先被实例化,没有被bpp托管,也就是没有办法实现aop操作,我说的可能不够清楚
贴几个网友的回复
Spring会实例化FactoryBean,以确定其创建的bean的类型。
由于ShiroFilterFactoryBean实现了FactoryBean接口,所以它会提前被初始化。又因为SecurityManager依赖Realm实现类、Realm实现类又依赖userLoginServiceImp,所以引发所有相关的bean提前初始化。
ShiroFilterFactoryBean -> SecurityManager -> Realm实现类 -> userLoginServiceImp
但是此时还只是ApplicationContext中registerBeanPostProcessors注册BeanPostProcessor处理器的阶段,此时AnnotationAwareAspectJAutoProxyCreator还没有注册到BeanFactory中,故无法对userLoginServiceImp进行事务处理和aop编程等操作。
参考链接1 参考链接2 参考链接3 参考链接4 目前现存的解决办法是在用户授权和登录的时候操作数据时不要直接使用service层的bean,直接通过dao层,避免了事务处理就不用担心事务处理了(这个问题不解决浑身难受啊,控制台里输出一大串,如果有解决办法有看到的人请联系我谢谢)
接下来写token
写token前先了解下什么是CSRF攻击
- CSRF是什么?
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。
- CSRF可以做什么?
你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…造成的问题包括:个人隐私泄露以及财产安全。
- CSRF漏洞现状
CSRF这种攻击方式在2000年已经被国外的安全人员提出,但在国内,直到06年才开始被关注,08年,国内外的多个大型社区和交互网站分别爆出CSRF漏洞,如:NYTimes.com(纽约时报)、Metafilter(一个大型的BLOG网站),YouTube和百度HI…而现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为“沉睡的巨人”。
- CSRF的原理
下图简单阐述了CSRF攻击的思想:
从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤:
1. 登录受信任网站A,并在本地生成Cookie。
2. 在不登出A的情况下,访问危险网站B。
看到这里,你也许会说:“如果我不满足以上两个条件中的一个,我就不会受到CSRF的攻击”。是的,确实如此,但你不能保证以下情况不会发生:
1. 你不能保证你登录了一个网站后,不再打开一个tab页面并访问另外的网站。
2. 你不能保证你关闭浏览器了后,你本地的Cookie立刻过期,你上次的会话已经结束。(事实上,关闭浏览器不能结束一个会话,但大多数人都会错误的认为关闭浏览器就等于退出登录/结束会话了......)
3.上图中所谓的攻击网站,可能是一个存在其他漏洞的可信任的经常被人访问的网站。
上面大概地讲了一下CSRF攻击的思想 转载自CSRF攻击 _作者hyddd 接下来开始写
TokenUtils
public class TokenUtils {
//加密的密钥
private static final String secret = "LEEMHT";
//获取token的key,一般token存在请求头和响应投中
public static final String tokenHead = "mhtTokenHead";
//token有效时间
private static final Long expTime = 24*60*60*1000L;
public static String getToken(String username,String password){
JwtBuilder builder = Jwts.builder();
//设置加密方式
builder.signWith(SignatureAlgorithm.HS256,secret)
//设置关键信息
.setSubject(username)
//防止密码(暂不确定能不能取就不放了)
//.setSubject(password)
//设置签发时间
.setIssuedAt(new Date())
//设置有效时间
.setExpiration(new Date(System.currentTimeMillis() + expTime));
//生成
String token = builder.compact();
log.info("token = " + token);
return token;
}
/**
* 查看并解析token
* 这个方法会在token异常的时候自动抛出异常,不用自定异常,
* 只需要在验证的时候进行捕获即可
* @param token
* @return
*/
public static Claims getTokenBody(String token){
//这里得到是token中的载荷部分,也是具体信息的所在
Claims claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token).getBody();
return claims;
}
public static String getUsername(String token) {
String username = getTokenBody(token).getSubject();
log.info(username);
return username;
}
}
systemServiceImpl
@Override
public ResultObj login(@Param("username") String username, @Param("password") String password) {
try {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken uptoken = new UsernamePasswordToken(username,password);
subject.login(uptoken);
//查询权限,现在还没写,先留个空
//获取token
String token = TokenUtils.getToken(username, password);
//返回登陆成功
return new ResultObj(Constants.OK,"登陆成功",token);
}catch (Exception e) {
e.printStackTrace();
return new ResultObj(Constants.ERROR,"登陆失败",null);
}
}
验证 写前端去喽
一个小时
完成了收到token后自动登录的功能 Login.vue中的toLogin函数
toLogin(){
let _this = this
_this.loading = true
let autoLogin = _this.loginForm.autoLogin
let data = {
username:_this.loginForm.username,
password:_this.$MD5(_this.loginForm.password.trim()),
}
console.log(data)
userApi.login(data).then(res => {
let result = res.data
if(result.code == 200){
_this.$store.dispatch("getUserInfoByUsername",_this.loginForm.username);
let token = result.data
//如果自动登录就把token 存入localStorage,这样一打开也不用登录
if(autoLogin){
window.localStorage.setItem("mhtToken", token);
}
//如果不是自动登录的话,刷新一下页面就没,目前逻辑是这么写的,虽然这样很蠢,但也算实现了功能
//打算之后写成1小时内保持登陆状态,这得加上跟后端的校验,前端纯写这样的逻辑不符合前后端分离的思想
else{
window.localStorage.removeItem("mhtToken")
}
//首先将token 存到vuex中
_this.$store.dispatch("setToken",token)
_this.$message({
type:"success",
message: result.msg,
center:true,
duration:500,
onClose:function(){
//跳转至首页
_this.$router.replace("/")
}
})
} else {
//登陆失败清空password框
_this.$message({
typre:"error",
message:result.msg,
center:true,
duration:500,
onClose:function(){
_this.loading = false
_this.loginForm.password = ""
}
})
}
})
},
vuex=>strore
//存取数据的位置
const state = {
token: localStorage.getItem("mhtToken")
? localStorage.getItem("mhtToken")
: ""
};
//用来处理异步请求
const actions = {
setToken(context, token) {
context.commit("SETTOKEN", token);
},
getUserInfoByUsername(context, username) {
console.log(username);
}
};
//写入state
const mutations = {
SETTOKEN(state, token) {
state.token = token;
}
};
|