<!--SpringSecurity依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- SpringSecurity默认环境中,拦截所有请求,要求必须认证(登录)后才能访问。
- 默认环境中,用户名是user,每次启动的时候,动态生成一个长度128字节的密码。在控制台输出。
- 默认环境中,提供登录页面的请求地址是/login, 请求方式是GET
- 默认环境中,处理登录请求的地址是/login, 请求方式是POST
# 静态用户,一般只用在内部网络认证中使用。如:内部服务器1访问内部服务器2.
spring:
security:
user:
name: monologue #通过配置文件,设置静态用户名
password: monologue #通过配置文件,设置静态登录密码
/**
* Created by Monologue_zsj on 2021/4/14 14:24
* Author:小脸儿红扑扑
* Description:SpringSecurity需要使用的服务对象,
* SpringSecurity提供了一个接口,命名为UserDetailsService
* 其中定义了唯一的认证方法,命名为:loadUserByUsername
* 方法代表,用户认证。
* 抛出异常,UsernameNotFoundException,用户查询失败的时候,抛出。
* 用户查询,是只用用户名作为条件,查询用户。
* SpringSecurity内部自动匹配密码是否正确的时候,一定要进行加密及解密的处理。
* 要求Spring容器中,必须存在一个PasswordEncoder实现类对象。
* 对象中,提供加密逻辑和解密逻辑。解密逻辑不是要求反向计算明文,而是要求实现实现明文和密文的验证。
* 解密验证可以,加密验证也可以。
*/
@Component
public class SpringSecurityConfiguration implements UserDetailsService {
@Autowired
private UserService userService;
/**
*
* @param username 用户登录的用户名
* @return
* @throws UsernameNotFoundException 当用户查询失败的时候,抛出的异常
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.login(username);
if (user == null) {
//查询失败,用户名不存在。抛出异常。
throw new UsernameNotFoundException("用户名错误...");
}
/**
* 查询成功,用户存储。需要匹配用户名密码是否正确。
* 匹配密码,是由SpringSecurity内部逻辑自动完成。只需要把查询的用户名正确密码返回即可。
* 返回结果,是UserDetails类型的实现类。可以自定义,可以使用Security框架。
* security框架提供的UserDetails接口实现类型User,构造的时候,需要提供3个参数,或者7个参数。
* 3参数构造:
* 用户身份(用户名)、用户正确密码(数据库中的密码),权限集合(Collection集合)
* AuthorityUtils - 工具类,可以通过字符串,创建权限集合。
*/
org.springframework.security.core.userdetails.User result
= new org.springframework.security.core.userdetails.User(
username,
user.getPassword(),
AuthorityUtils.createAuthorityList());
return result;
}
}
/**
* Created by Monologue_zsj on 2021/4/14 16:36
* Author:小脸儿红扑扑
* 第二步:
* Description:凭证匹配器,专门用于做认证流程的凭证校验使用的类型。
* 其中有两个核心方法:
* 1、encode - 把明文加密成密文密码
* 2、matches - 校验明文和密文是否匹配
*/
public class SimplePasswordEncoder implements PasswordEncoder {
/**
* 加密逻辑
* @param rawPassword 明文字符串,就是页面收集的源密码
* @return
*/
@Override
public String encode(CharSequence rawPassword) {
System.out.println("加密逻辑执行...");
return new String(rawPassword.toString());
}
/**
* 匹配逻辑
* @param rawPassword 明文,页面收集的密码
* @param encodedPassword 密文,存储在数据库中的密码
* @return
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
System.out.println("密码匹配逻辑执行...");
return encodedPassword.equals(encode(rawPassword));
}
}
/**
* Created by Monologue_zsj on 2021/4/14 17:04
* Author:小脸儿红扑扑
* Description:MD5加密Encoder,替换上面的SimplePasswordEncoder
*/
public class MyMD5PasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] tmp = digest.digest(rawPassword.toString().getBytes());
return toHexString(tmp);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(encode(rawPassword));
}
private String toHexString(byte [] tmp) {
StringBuilder builder = new StringBuilder();
for (byte b : tmp) {
String string = Integer.toHexString(b & 0xFF);
if (string.length() == 1) {
builder.append("0");
}
builder.append(string);
}
eturn builder.toString();
}
}
/**
* Created by Monologue_zsj on 2021/4/14 16:54
* Author:小脸儿红扑扑
* Description:加密工具
*/
public class TestMD5 {
public static void main(String[] args) {
/*强散列加密*/
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
System.out.println("admin - " + encoder.encode("admin"));
System.out.println("admin1 - " + encoder.encode("admin1"));
System.out.println("admin2 - " + encoder.encode("admin2"));
}
/*MD5加密*/
public static void testMd5() throws NoSuchAlgorithmException {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] digest1 = md5.digest("admin".getBytes());
byte[] digest2 = md5.digest("admin1".getBytes());
byte[] digest3 = md5.digest("admin2".getBytes());
System.out.println("admin - " + toString(digest1));
System.out.println("admin1 - " + toString(digest2));
System.out.println("admin2 - " + toString(digest3));
}
private static String toString(byte [] tmp) {
StringBuilder builder = new StringBuilder();
for (byte b : tmp) {
String string = Integer.toHexString(b & 0xFF);
if (string.length() == 1) {
builder.append("0");
}
builder.append(string);
}
return builder.toString();
}
}
/**
* Created by Monologue_zsj on 2021/4/14 16:49
* Author:小脸儿红扑扑
* 第三步:
* Description:
*/
@Configuration
public class MySpringSecurityConfiguration {
@Bean
public PasswordEncoder passwordEncoder() {
// return new SimplePasswordEncoder();
// return new MyMD5PasswordEncoder();
return new BCryptPasswordEncoder();
}
}
/**
* Created by Monologue_zsj on 2021/4/14 16:49
* Author:小脸儿红扑扑
* 第三步:
* Description:
*/
@Configuration
public class MySpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* 重写父类中的配置逻辑
* 如果调用了super.configure,则使用父类型的默认流程配置
* 需要自定义,则删除super.configure方法的调用
* 当提供自定义配置,删除super.configure方法的时候,所有的默认流程全部清空
* http.formLogin.loginProcessingUrl(String path);
* 用户登录请求地址,就是处理用户登录逻辑的地址。
* SpringSecurity提供的处理登录请求控制器,是path监听软编码的。可以通过此方法动态配置监听地址。
* 只要配置地址和页面的请求地址一致,即可完成登录逻辑。
* 登录成功,默认返回的页面是 '/',如果有前置请求,则默认返回前置地址
* 登录失败,默认返回的页面是 http.formLogin().loginPage(String path) 方法参数?error
* http.formLogin().defaultSuccessUrl(String path, boolean alwaysUse)
* 设置登录成功后,响应重定向地址,SpringSecurity要求,必须传递绝对路径,
* 如果传递的是相对路径,则相对于当前服务器的根开始寻址。http://localhost:port/
* 参数alwaysUse可以省略。默认是false。配置为false的时候,通常重定向失效。
*
* 登录请求参数命名:
* 默认规则是:用户名必须是username,密码必须是password
* 此逻辑由UsernamePasswordAuthenticationFilter决定。
*
* @param http 基于Http协议的Security配置对象,包含所有的SpringSecurity相关逻辑配置
* @throws Exception 当配置出错的时候抛出
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置登录请求相关内容
http.formLogin()
//.usernameParameter("user") //设置请求参数中,用户名参数名称 默认username
//.passwordParameter("pwd") //设置请求参数中,密码参数名称 默认password
.loginPage("/toLogin") //当用户未登录的时候,跳转的登录页面地址是什么,默认/login
.loginProcessingUrl("/login") //用户登录逻辑请求地址是什么,默认 /login
.successHandler(new MyAuthenticationSuccessHandler("/toMain", true)) //使用自定义请求处理控制逻辑(Handler)实现登录成功后的请求处理,需要AuthenticationSuccessHandler类型的对象
.failureHandler(new MyAuthenticationFailureHandler("/failure", true)); //自定义认证失败后的处理逻辑
//.failureUrl("/failure"); //登录失败后,重定向的位置。记得提供权限配置校验
//.failureForwardUrl("failure"); //登录失败后,请求转发的位置。Security请求转发使用POST请求
//.defaultSuccessUrl("/toMain", true); //用户登录成功后,响应重定向到的位置,GET请求
//.successForwardUrl("/toMain"); //用户登录成功后,请求转发到的位置。Security请求转发使用POST请求
//配置权限校验,如:什么地址 必须认证后才能访问,什么地址可以不用认证就可以访问
//权限校验的配置,是线性的。从开始的配置位置开始校验,成功立刻返回。
//校验匹配失败,继续后续的逻辑校验
http.authorizeRequests()
.antMatchers("/toLogin", "/failure").permitAll() //toLogin请求地址,可以随便访问
.anyRequest().authenticated(); //任意的请求必须认证后才可以访问
//关闭CSRF安全协议,关闭是为了完整流程的可用
http.csrf().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
// return new SimplePasswordEncoder();
// return new MyMD5PasswordEncoder();
return new BCryptPasswordEncoder();
}
}
/**
* Created by Monologue_zsj on 2021/4/14 21:14
* Author:小脸儿红扑扑
* Description:自定义登录成功后的处理器
* 转发或者重定向,由代码逻辑实现
*/
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
private boolean isRedirect;
public MyAuthenticationSuccessHandler(String url, boolean isRedirect) {
this.url = url;
this.isRedirect = isRedirect;
}
/**
* @param request 请求对象 request.getRequestDispatcher().forward()
* @param response 响应对象 response.sendRedirect();
* @param authentication 用户认证(登录)成功后的对象。
* 其中包含用户名和权限集合,
* 内容是自定义的UserDerails实现的方法值对象提供的
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if (isRedirect) {
response.sendRedirect(url);
}else {
request.getRequestDispatcher(url).forward(request, response);
}
}
/*getter和setter方法不是必要的*/
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isRedirect() {
return isRedirect;
}
public void setRedirect(boolean redirect) {
isRedirect = redirect;
}
}
/**
* Created by Monologue_zsj on 2021/4/14 22:21
* Author:小脸儿红扑扑
* Description:自定义登录失败后的处理器
*/
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String url;
private Boolean isRedirect;
public MyAuthenticationFailureHandler(String url, Boolean isRedirect) {
this.url = url;
this.isRedirect = isRedirect;
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
if (isRedirect) {
response.sendRedirect(url);
}else {
request.getRequestDispatcher(url).forward(request, response);
}
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Boolean getRedirect() {
return isRedirect;
}
public void setRedirect(Boolean redirect) {
isRedirect = redirect;
}
}