Spring Security因为它的复杂,所以从功能的丰富性的角度更胜一筹:
Spring Security的HttpBasic模式,比较简单,只是进行了通过携带Http的Header进行简单的登录验证,而且没有定制的登录页面,所以使用场景比较窄。
所以这里重点介绍FormLogin模式:
formLogin模式的三要素:
登录认证逻辑
资源访问控制规则,如:资源权限、角色权限
用户角色权限
server:
port: 8888
servlet:
session:
timeout: 10s #Session的超时时间,默认最小1分钟,小于1分钟也是1分钟
cookie:
http-only: true # 浏览器脚本无法访问cookie 安全
secure: false #必须用https才能发送cookie
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/jpadata?useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
freemarker:
cache: false # 缓存配置 开发阶段应该配置为false 因为经常会改
suffix: .html # 模版后缀名 默认为ftl
charset: UTF-8 # 文件编码
template-loader-path: classpath:/templates/
security:
loginType: JSON
user:
name: admin
password: admin
logging:
config: classpath:log4j2-dev.xml
mybatis:
configuration:
mapUnderscoreToCamelCase: true
package com.springSecurityDemo.basicserver.config;
import com.springSecurityDemo.basicserver.config.auth.MyAuthenticationFailureHandler;
import com.springSecurityDemo.basicserver.config.auth.MyAuthenticationSuccessHandler;
import com.springSecurityDemo.basicserver.config.auth.MyExpiredSessionStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
MyAuthenticationSuccessHandler mySuthenticationSuccessHandler;
@Resource
MyAuthenticationFailureHandler myAuthenticationFailureHandler;
//重写Springsecuriry配置,以达到自定义的需求
@Override
protected void configure(HttpSecurity http) throws Exception {
//httpBasic模式的设置 保护所有请求,作用不大,一般不用
//因为很容易破解
// http.httpBasic()
// .and()
// .authorizeRequests().anyRequest() //所有请求
// .authenticated(); //必须登录后才能访问
// }
//formLogin模式
//关闭跨站请求保护
http.csrf().disable()
.formLogin()
//登陆逻辑配置:
//登陆页面,
.loginPage("/login.html")
.usernameParameter("uname") //登录表单form中用户名输入框input的name名,不修改的话默认是username
.passwordParameter("pword") //form中密码输入框input的name名,不修改的话默认是password
//登录表单form中action的地址,也就是处理认证请求的路径
.loginProcessingUrl("/login") //拦截前端的登陆请求,交由SpringSecurity控制
//.defaultSuccessUrl("/index") //登陆成功做页面跳转
//.failureUrl("/login.html") //登陆失败页面
.successHandler(mySuthenticationSuccessHandler) //自定义登陆成功处理结果与defaultSuccessUrl("/index")互相排斥
.failureHandler(myAuthenticationFailureHandler) //自定义登陆失败逻辑
.and()
//权限校验规则
.authorizeRequests()
//login页面和login的url谁都可以访问
.antMatchers("/login.html","/login").permitAll()
.antMatchers("/biz1","/biz2") //需要对外暴露的资源路径
.hasAnyAuthority("ROLE_user","ROLE_admin") //user角色和admin角色都可以访问
//.antMatchers("/syslog","/sysuser")
//.hasAnyRole("admin") //admin角色可以访问
//.hasAnyAuthority("ROLE_admin")
.antMatchers("/syslog").hasAuthority("sys:log") //通过权限id控制权限,有该权限id才能访问
.antMatchers("/sysuser").hasAuthority("sys:user") //自定义key值,通过key值进行访问
.anyRequest().authenticated()
//Session的管理
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) //默认的也是需要的时候才会创建Session
.invalidSessionUrl("/login.html") //Session超时登陆跳转的页面
.sessionFixation().migrateSession() //每次Session复制一份,重新生成JSessionID,保障安全
.maximumSessions(1) //最大当前只能有一个用户登陆
.maxSessionsPreventsLogin(false) //允许再次登陆,之前登陆的会被踢掉
.expiredSessionStrategy(new MyExpiredSessionStrategy()); //引入我们自定义的超过在线人数的回调函数
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//静态配置用户
auth.inMemoryAuthentication()
.withUser("user")
.password(passwordEncoder().encode("123456"))
.roles("user")
.and()
.withUser("admin")
.password(passwordEncoder().encode("123456"))
.authorities("sys:log","sys:user") //赋予资源id,放行其访问资源
//.roles("admin")
.and()
.passwordEncoder(passwordEncoder());//配置BCrypt加密
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) {
//将项目中静态资源路径开放出来
web.ignoring()
.antMatchers( "/css/**", "/fonts/**", "/img/**", "/js/**");
}
}
package com.springSecurityDemo.basicserver.config.auth;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springSecurityDemo.basicserver.config.exception.AjaxResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登陆成功结果处理
*/
//通常我们不会直接继承AuthenticationSuccessHandler
//继承SavedRequestAwareAuthenticationSuccessHandler 能 跳转到登陆之前未登陆请求的页面
@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Value("${spring.security.loginType}") //通过@value加载配置文件中的参数
private String loginType;
//JACKjson的 对象与json字符串转换类
private static ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
if(loginType.equalsIgnoreCase("JSON")){ //如果配置文件里是JSON,那么就用JSON方式做响应
//JSon的数据格式进行数据相应
response.setContentType("application/json;charset=UTF-8");
//objectMapper.writeValueAsString 对象转换成字符串
response.getWriter().write(objectMapper.writeValueAsString(
AjaxResponse.success("/index")
));
}else{
//跳转到登陆之前请求的页面(记录上一次登陆后的请求,如果登陆成功还会跳转到上一次跳转到的页面)
super.onAuthenticationSuccess(request,response,authentication);
}
}
}
package com.springSecurityDemo.basicserver.config.auth;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springSecurityDemo.basicserver.config.exception.AjaxResponse;
import com.springSecurityDemo.basicserver.config.exception.CustomException;
import com.springSecurityDemo.basicserver.config.exception.CustomExceptionType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//SimpleUrlAuthenticationFailureHandler 登陆失败之后默认跳转到登陆页
@Component
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Value("${spring.security.loginType}")
private String loginType;
private static ObjectMapper objectMapper = new ObjectMapper();
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
if(loginType.equalsIgnoreCase("JSON")){
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(
AjaxResponse.error(new CustomException(
CustomExceptionType.USER_INPUT_ERROR,
"用户名或者密码输入错误!"))
));
}else{
//跳转到登陆页面
super.onAuthenticationFailure(request,response,exception);
}
}
}
package com.springSecurityDemo.basicserver.config.auth;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
*SessionInformationExpiredStrategy
* 实现接口里面的onExpiredSessionDetected方法,当Session超时或非法的时候就会回调
*
*/
public class MyExpiredSessionStrategy implements SessionInformationExpiredStrategy {
private static ObjectMapper objectMapper = new ObjectMapper();
//当Session超时或非法的时候就会回调
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
Map<String,Object> map = new HashMap<>();
map.put("code",0);
map.put("msg","您已经在另外一台电脑或浏览器登录,被迫下线!");
//event将自定义错误提示写回(或跳转页面类似)
event.getResponse().setContentType("application/json;charset=UTF-8");
event.getResponse().getWriter().write(
objectMapper.writeValueAsString(map)
);
}
}