日志与定时任务
一、权限管理
拦截器方式
自定义拦截器,判断是否得到凭证,没有,则拦截
//自定义拦截器类
public class MyHandler implements HandlerInterceptor {
@Override //拦截器的拦截处理的handler
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object o = request.getSession().getAttribute("username");
if(o==null){ //没有登录凭证,则到达登录页面
response.sendRedirect("/login.html");
return false;
}
return true;
}
}
在mvc容器中配置拦截路径
<mvc:interceptors> <!-- 配置拦截路径 -->
<mvc:interceptor>
<mvc:mapping path="/customer/**"/>
<mvc:mapping path="/app/**"/>
<bean class="com.qf.handler.MyHandler"></bean>
</mvc:interceptor>
</mvc:interceptors>
登录用户模块的表设计
create table user(
id int primary key auto_increment,
username varchar(20),
password varchar(20)
);
insert into user(username,password) values('zs','123');
insert into user(username,password) values('ls','444');
再创建controller,service,dao
前端页面创建login.html并准备标签
<div class="layui-row" style="margin-top:10%">
<div class="layui-col-xs6 layui-col-md-offset2">
<form class="layui-form" action="#" method="post">
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" name="username" value="zs" lay-verify="text" autocomplete="off" placeholder="请输入账号"
class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="text" name="password" value="123" lay-verify="required" placeholder="请输入密码" autocomplete="off"
class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="login">登录</button>
<span id="msg"></span>
</div>
</div>
</form>
</div>
</div>
再使用提交的触发事件,通过ajax与后端交互
layui.use(['form'], function(){
var form = layui.form;
//监听提交
form.on('submit(login)', function(data){
layer.msg(JSON.stringify(data.field));
$.ajax({
type:"post",
url:"/user/login",
data:data.field,
success:function (res) {
if(res==1){ //登录成功,则跳到首页
location.href="/index.html";
}else if(res==0){
$("#msg").html("<font color='red'>登录失败</font>");
}
}
})
return false;
});
});
在首页判断是否有凭证
function openRight(url) {
//在加载数据前,先判断是否有登录凭证;如果没有,跳到登录;有则加载
$.ajax({
type:"get",
url:"/user/valid",
success:function(res){
if(res==0){ //凭证校验失败
location.href="/login.html"; //调整
}else if(res==1){
$("#main").load(url); //main就是用于显示数据的div的id
}
}
})
}
拦截细节
前端都是使用html页面,所有如果需要凭证的判断,都需要通过ajax发请求。
拦截的是客户模块,应用模块;用户模块本身是放行的,没有进入拦截路径。
拦截测试:
- 直接访问/customer/show; 拦截住则进入登录
- 点击客户管理和应用管理的测试,拦截住,则进入登录(需要在首页中进行判断凭证)
二、日志配置
日志概述:在项目开发和上线的过程中,都离不开日志;开发时日志信息打印到控制台,可以分析程序的执行情况;上线后通过分析日志,可查找异常的情况。
日志级别:从低到高分别是:trace,debug,info,warn,error,fatal
配置
导包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
日志测试
public class Log4jTest {
public static void main(String[] args) {
//默认指定了info级别打印,所以info以后的信息都出现
Log log = LogFactory.getLog(Log4jTest.class);
log.trace("trace log"); //跟踪日志
log.debug("debug log"); //调试日志
log.info("info log"); //信息日志
log.warn("warn log"); //警告日志
log.error("error log"); //错误日志
log.fatal("fatal log"); //致命日志
}
}
应用日志
在项目中使用日志;需要自己定制打印出不同的级别;后续根据打印的日志级别进行分析问题
@GetMapping("/show")
public TableData<Customer> show(int page,int limit,Customer customer){
//日志的应用:
Log log = LogFactory.getLog(CustomerController.class);
if(page==1){
log.info("page one info!!");
}else if(page==2){
log.info("page two info!!!");
}
try {
int i=1/0;
}catch (Exception e){
log.error("arth exception!!");
}
PageInfo<Customer> pi = customerService.selectCustomers(page,limit,customer);
TableData<Customer> td = new TableData<>(0,"no data",pi.getTotal(),pi.getList());
return td;
}
说明:日志的级别配置,都是自己定制的;如果需要将日志配置到文件中,则需要在resources中放置log4j.xml配置文件。
三、定时任务
概述:定制在某个时间点,触发某种执行任务
应用场景:每月还花呗,每天的闹钟触发(类似之前前端讲的定时器)
三个核心组件:任务(Job)、触发器(定制规则)、调度器(启动任务)
测试案例
导入依赖包
<dependencies>
<!--Quartz任务调度-->
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
</dependencies>
定时任务的执行
//定制定时任务实现Job接口
public class MyQuartz implements Job {
@Override //重写方法中就是需要执行的任务
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail detail = context.getJobDetail(); //获取任务详情
String name = detail.getKey().getName();
String group = detail.getKey().getGroup();
System.out.println("任务名:"+name+";任务组:"+group);
System.out.println(new Date()); //后续在项目中,可能是触发执行某个功能
}
}
三个核心组件去配置规则:调度器,任务,触发器
//创建触发器,定制触发规则
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("tri1","gro1")
.startNow() //立即启动触发器规则
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3) //每隔3秒触发
.repeatForever()) //一直持续触发 //触发规则
//结束时间:指定年月日,时分秒
.endAt(new GregorianCalendar(2023,10,20,12,0,0).getTime()) //结束时间
.build();
//创建任务详情,将要执行的任务的反射对象引入
JobDetail detail = JobBuilder.newJob(MyQuartz.class)
.withIdentity("job1","job_gro1")
.build();
//创建调度器,并启动
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(detail,trigger); //调度任务,传入触发器和任务对象
scheduler.start(); //启动
CronTrigger
前面的Trigger只是处理一些简单的触发规则;如果需要更灵活及强大的触发器,则需要使用CronTrigger。
//CronTrigger触发器
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t1","g1")
.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?"))
.build();
cron表达式
cron表达式,通过:秒 分 时 日 月 星期 [年] ,来定制不同的规则。
例如:下面的荣表达式,表示每日12点触发; 解析:从左往右解读,*代表“每”
0 0 12 * * ? : 秒 分 时 日 月 星期
| 表示式 | 说明 |
| ------------------------ | ------------------------------------------------------------ |
| 0 0 12 * * ? | 每天12点运行 |
| 0 15 10 * * ? | 每天10:15运行 |
| 0 15 10 * * ? 2008 | 在2008年的每天10:15运行 |
| 0 * 14 * * ? | 每天14点到15点之间每分钟运行一次,开始于14:00,结束于14:59。 |
| 0 0/5 14 * * ? | 每天14点到15点每5分钟运行一次,开始于14:00,结束于14:55。 |
| 0 0/5 14,18 * * ? | 每天14点到15点每5分钟运行一次,此外每天18点到19点每5钟也运行一次。 |
| 0 0-5 14 * * ? | 每天14:00点到14:05,每分钟运行一次。 |
| 0 0-5/2 14 * * ? | 每天14:00点到14:05,每2分钟运行一次。 |
| 0 10,44 14 ? 3 4 | 3月每周三的14:10分和14:44分,各运行一次。 |
| 0 15 10 ? * 2-6 | 每周一,二,三,四,五的10:15分运行一次。 |
| 0 15 10 15 * ? | 每月15日10:15分运行。 |
| 0 15 10 L * ? | 每月最后一天10:15分运行。 |
| 0 15 10 ? * 6L | 每月最后一个星期五10:15分运行。【此时天必须是"?"】 |
| 0 15 10 ? * 6L 2007-2009 | 在2007,2008,2009年每个月的最后一个星期五的10:15分运行。 |
spring整合任务
和测试案例类似,将三个核心组件以bean方式注入数据。
导包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
spring的容器配置
<!-- 产生任务bean -->
<bean id="myjob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="name" value="j1"></property>
<property name="group" value="g1"></property>
<property name="jobClass" value="com.qf.handler.MyQuartz"></property>
</bean>
<!-- 产生cron触发器 -->
<bean id="mytri" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="name" value="t1"></property>
<property name="group" value="tg1"></property>
<!-- 触发器中绑定任务 -->
<property name="jobDetail" ref="myjob"></property>
<!-- 触发器规则-->
<property name="cronExpression" value="0/3 * * * * ?"></property>
</bean>
<!-- 产生调度器 -->
<bean id="schedur" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers"> <!-- 容器中引入触发器bean -->
<list>
<ref bean="mytri"/>
</list>
</property>
</bean>
四、shiro
概述
shiro是一套安全框架;应用包括:身份认证(类似拦截器的用法),用户授权
shiro和spring security类似,都是做认证和授权的安全框架。
区别:shiro可以脱离框架来应用;但spring security必须有spring框架才能使用。
核心功能
核心功能有4个,分别是认证,授权,会话管理和加密:
- 认证(Authentication):登录,证明用户是谁的行为。
- 授权(Authorization):访问控制的过程,即确定“谁可以访问”什么。
- 会话管理(Session Management):即使在非Web或EJB应用程序中也可以管理用户特定的会话。
- 密码学(Cryptography):使用加密算法保持数据安全性,同时易于使用。
身份认证
导入依赖包
<!-- shiro核心支持 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro Web支持 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro Spring支持 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro 缓存支持 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>
web.xml中的shiro过滤器配置
<!-- Shiro Filter -->
<filter>
<!--
DelegatingFilterProxy 过滤器代理对象,默认情况下spring会ioc中找和filter-name,相同的bean
可以使用initparam 配置tagerbeanname找对应的bean(spring需要的配置对象),
建议不要改
-->
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
bean-shiro容器中的配置
<!-- 1.配置SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- cacheManager缓存管理器 -->
<property name="cacheManager" ref="cacheManager"/>
<!-- session管理方式 -->
<property name="sessionMode" value="native"/>
<!-- <property name="realm" ref="jdbcRealm"/>-->
</bean>
<!-- cacheManager缓存管理器 需要ehcache的jar包以及配置文件 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!-- 直接指定默认的ehcache -->
<!--<property name="cacheManager" ref="ehCacheManager"/> -->
<!-- 通过ehcache配置缓存 -->
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- 3.配置realm -->
<!-- 3.1 配置 实现了Realm接口的类 -->
<bean id="jdbcRealm" class="com.cos.shiro.realm.MyRealm">
<!-- @TODO 设置Realm 创建类继承org.apache.shiro.realm.AuthenticatingRealm,可以先写空的来测试框架组合是否成功 -->
</bean>
<!-- Shiro生命周期处理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after
the lifecycleBeanProcessor has run: -->
<!-- 使用shiro注解,前提为需要先配置 LifecycleBeanPostProcessor,并且如果注解设置在controller需要配置到spring-mvc里面 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- 配置 shiro过滤器,真正处理客户端请求的类,id值必须和web.xml配置的 shiroFilter 的filtername一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 安全管理器 -->
<property name="securityManager" ref="securityManager"/>
<!-- 登陆页面,如果认证没有通过会重定向到这个页面 -->
<property name="loginUrl" value="/login.jsp"/>
<!-- 登陆成功页面 -->
<property name="successUrl" value="/index.jsp"/>
<!-- 授权失败跳转的页面 -->
<property name="unauthorizedUrl" value="/login.jsp"/>
<!-- 过滤器链 ,用于配置那些页面需要验证保护,以及访问这些页面需要的权限-->
<property name="filterChainDefinitions">
<!-- 注意:过滤器的工作顺序:shiro采取第1次匹配优先,第一次匹配后后面的过滤器链不会匹配,
顺序不当可能出现”302 not found”错误 常用过滤器通俗解释:(过滤路径支持ant风格)
logout :退出登陆过滤器:默认跳转项目根路径访问地址(可自定义)
anon:匿名过滤器:不需要认证就可以进行访问:例如公司的首页
authc:认证过滤器:客户端只有认证通过之后才能访问(例如个人信息)
roles:权限过滤器:客户端访问制定路径,只有满足指定的角色才能访问 -->
<value>
/login.jsp = anon
/user/login = anon
/user/logout=logout
/** = authc
</value>
</property>
</bean>
再将缓冲配置文件ehcache.xml导入即可。
过滤规则:
<value>
/login.jsp = anon <!--登录是放行的,任何用户都能访问-->
/user/login = anon <!--后台路径页放行-->
/user/logout=logout <!--处理缓存-->
/** = authc <!--其它的路径访问都需要认证-->
</value>
认证流程
- Controller
- 获取当前的Subject即访问用户抽象对象,调用SecurityUtils.getSubject()
- 根据传入的username及password通过token进行登录认证
- 具体的登录认证,通过realm完成
- realm
- 获取subject中token对象,取出用户名
- 调用数据库方法返回对象,查询username是否存在
- 如用户不存在则抛出异常
控制层传入用户名和密码,并交给token存数据
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public String login(String username,String password){
System.out.println("username-->"+username+";password-->"+password);
//获取subject对象--用户对象
Subject subject = SecurityUtils.getSubject();
if(!subject.isAuthenticated()){//如果没有认证成功
//传入用户名和密码生成token对象(共享对象),在realm中可获取
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
subject.login(token); //登录认证
}
return "redirect:/index.html";
}
}
subject调用登录方法,会进入realm类中进行身份认证;因为在realm中可连接数据库
public class MyRealm extends AuthenticatingRealm {
@Resource
private UserDao userDao;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//token强转,即可调用属性username 和控制层的token是同一个对象
UsernamePasswordToken utoken = (UsernamePasswordToken) token;
//根据用户名获取user对象 用户名的校验
User user = userDao.selectByName(utoken.getUsername());
if(user==null){
throw new UnknownAccountException("用户名不存在");
}
//密码校验
SimpleAuthenticationInfo auth = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),getName());
return auth;
}
}
五、总结与作业
总结
1.权限管理
使用拦截器方式处理;前端都是html,通过ajax方式交互
拦截细节
2.日志配置
导包-测试log4j;应用到项目中(自己定制)
3.定时任务
三个核心组件(任务,触发器,调度器)(重点)
测试案例;conTrigger的使用
cron表达式(重点);spring整合任务
4.shiro
概述; 核心功能:认证,授权(重点),还有会话与加密
身份认证的应用与执行流程
作业
登录成功,记录凭证,并在首页显示用户名; 点击“退了”,则清除用户名,显示“请登录”;点击“请登录”,则可跳到登录页面