一.搭建项目
二.代码实例
注意:与三相比关键是过滤器链写法不同
2.1 SecurityConfig 配置(关键)
注意:
1.用户名或者密码单独报错的异常是不会进入过滤器链的即if (e instanceof BadCredentialsException) 和else if (e instanceof UsernameNotFoundException) ,默认情况下,这个分支是不会进来的,Spring Security 自动隐藏了了这个异常,如果系统中发生了 UsernameNotFoundException 会被自动转为 BadCredentialsException 异常然后抛出来(就是我们在二中提到的默认隐藏异常)
2.csrf().disable() 关闭 csrf 防御机制,这个 disable 方法本质上就是从 Spring Security 的过滤器链上移除掉 csrf 过滤器(否则我们这里访问不到页面的)
package com.huang.springsecurity.comments.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.huang.springsecurity.comments.result.RespBean;
import com.huang.springsecurity.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
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.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.SecurityFilterChain;
import java.io.PrintWriter;
@Configuration
public class SecurityConfig {
@Bean
WebSecurityCustomizer securityCustomizer() {
return new WebSecurityCustomizer() {
@Override
public void customize(WebSecurity web) {
web.ignoring().antMatchers("/login.html");
}
};
}
/**
* 自己手动配置安全过滤器链
*
* @return
*/
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//开始认证
http.authorizeRequests()
//请求路径如果是 /login.html,则这个请求可以匿名通过
.antMatchers("/login.html").anonymous()
//所有的请求,类似于 shiro 中的 /**
.anyRequest()
//必须要认证之后才能访问,类似于 shiro 中的 authc
.authenticated()
.and()
//开始配置登录表单
.formLogin()
//配置登录页面,如果访问了一个需要认证之后才能访问的页面,那么就会自动跳转到这个页面上来
.loginPage("/login.html")
//配置处理登录请求的接口,本质上其实就是配置过滤器的拦截规则,将来的登录请求就会在过滤器中被处理
.loginProcessingUrl("/doLogin")
//配置登录表单中用户名的 key
.usernameParameter("username")
//配置登录表单中用户密码
.passwordParameter("password")
//配置登录成功后的跳转地址
// .defaultSuccessUrl("/hello")
// .failureUrl("/login.html")
//登录成功处理器
//req:当前请求对象
//resp:当前响应对象
//auth:当前认证成功的用户信息
.successHandler((req, resp, auth) -> {
resp.setContentType("application/json;charset=utf-8");
User principal = (User) auth.getPrincipal();
principal.setPassword(null);
RespBean respBean = RespBean.ok("登录成功", principal);
String s = new ObjectMapper().writeValueAsString(respBean);
resp.getWriter().write(s);
})
//登录失败的回调
.failureHandler((req, resp, e) -> {
resp.setContentType("application/json;charset=utf-8");
//登录失败可能会有多种原因
RespBean respBean = RespBean.error("登录失败");
if (e instanceof BadCredentialsException) {
respBean.setMsg("用户名或者密码输入错误,登录失败");
} else if (e instanceof UsernameNotFoundException) {
//默认情况下,这个分支是不会进来的,Spring Security 自动隐藏了了这个异常,如果系统中发生了 UsernameNotFoundException 会被自动转为 BadCredentialsException 异常然后抛出来
} else if (e instanceof LockedException) {
//如果 com.qfedu.security02.model.User.isAccountNonLocked 方法返回 false,就会进入到这里来
respBean.setMsg("账户被锁定,登录失败");
} else if (e instanceof AccountExpiredException) {
//com.qfedu.security02.model.User.isAccountNonExpired
respBean.setMsg("账户过期,登录失败");
} else if (e instanceof CredentialsExpiredException) {
respBean.setMsg("密码过期,登录失败");
} else if (e instanceof DisabledException) {
respBean.setMsg("账户被禁用,登录失败");
}
ObjectMapper om = new ObjectMapper();
String s = om.writeValueAsString(respBean);
PrintWriter out = resp.getWriter();
out.write(s);
})
.and()
//关闭 csrf 防御机制,这个 disable 方法本质上就是从 Spring Security 的过滤器链上移除掉 csrf 过滤器
.csrf().disable()
.exceptionHandling()
//如果用户未登录就访问某一个页面,就会触发当前方法
.authenticationEntryPoint((req, resp, authException) -> {
resp.setContentType("application/json;charset=utf-8");
RespBean respBean = RespBean.error("尚未登录,请登录");
String s = new ObjectMapper().writeValueAsString(respBean);
resp.getWriter().write(s);
});
return http.build();
}
}
2.2 service
package com.huang.springsecurity.service;
import com.huang.springsecurity.mapper.UserMapper;
import com.huang.springsecurity.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class UserService implements UserDetailsService, UserDetailsPasswordService {
@Autowired
UserMapper userMapper;
/**
* 根据用户名查询用户对象
* @param username 用户登录时候输入的用户名
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User u = userMapper.loadUserByUsername(username);
if (u == null) {
//说明用户名不存在
throw new UsernameNotFoundException("账户不存在");
}
return u;
}
}
2.2 mapper
UserMapper
package com.huang.springsecurity.mapper;
import com.huang.springsecurity.model.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
User loadUserByUsername(String username);
void updatePassword(User u);
}
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.huang.springsecurity.mapper.UserMapper">
<select id="loadUserByUsername" resultType="com.huang.springsecurity.model.User">
select * from t_user where username=#{username};
</select>
<update id="updatePassword">
update t_user set password=#{password} where id=#{id}
</update>
</mapper>