简介
Spring Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展了多年了,但是使用的并不多,安全管理这个领域,一直是 Shiro 的天下。
相对于 Shiro,在 SSM/SSH 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有 Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。
自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了 自动化配置方案,可以零配置使用 Spring Security。
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
启动会发现,需要登录了?
只要加了spring security依赖,项目中的所有接口都被保护起来了。
2.编写接口并进行测试
@Controller
public class SpringSecurityController {
@GetMapping("/hello")
@ResponseBody
public String hello(){
return "hello";
}
}
访问路径:http://localhost:8091/hello
跳转到登陆界面,这是因为加了spring security,项目中所有的接口就被保护起来了。
查看控制可以看到登陆密码:
密码是项目启动后随机生成的
Using generated security password: 6473da9b-b2c2-479a-93c3-6aaff431bb69
自定义登陆密码
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
//告诉系统不加密
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
//用户授权
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().//内存保存用户授权
withUser("lianlei").password("123").roles("admin")
.and()
.withUser("liubeibie").password("123").roles("admin");
}
一定要有用户密码不加密,否则有与前端传来的Hi加密的,后端没有加密,会发生异常
自定义登录页面
1.自定义页面 login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<title>登录</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/popper.js/1.12.5/umd/popper.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row" style="margin-top: 20px;">
<div class="col-md-3">
<h2>登陆</h2>
<form action="/doLogin" method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Enter username">
</div>
<div class="form-group">
<label for="Password">Password:</label>
<input type="password" class="form-control" id="Password" name="password" placeholder="Enter password">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
</body>
</html>
2.给登陆页面放行并且替换登陆页面
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/login.html","/js/**","/css/**").permitAll()//登陆页面放行
.anyRequest().authenticated()//其他页面需要登陆
.and()
.formLogin().loginProcessingUrl("/doLogin")//登陆路径
.loginPage("/login.html");//登陆页面,都要写页面
}
如果403过多,参考链接,因为首页没有放行。
登陆成功和失败处理器或者跳转路径
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/login.html","/js/**","/css/**").permitAll()//登陆页面放行
.anyRequest().authenticated()//其他页面需要登陆
.and()
.formLogin().loginProcessingUrl("/doLogin")//登陆路径
.loginPage("/login.html")//登陆页面
.usernameParameter("username")//前端传入的用户名
.passwordParameter("password")
.successHandler(new MysuccessHander())//登陆成功处理器
/*.failureHandler(((request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("登陆失败!");
System.out.println(request.getAuthType());
out.flush();
out.close();
}));*/
.failureForwardUrl("/login.html");
}
public class MysuccessHander implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("登陆成功!");
System.out.println(request.getAuthType());
out.flush();
out.close();
}
}
退出登陆
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/login.html","/js/**","/css/**").permitAll()//登陆页面放行
.anyRequest().authenticated()//其他页面需要登陆
.and()
.formLogin().loginProcessingUrl("/doLogin")//登陆路径
.loginPage("/login.html")//登陆页面
.usernameParameter("username")//前端传入的用户名
.passwordParameter("password")
.successHandler(new MysuccessHander())//登陆成功处理器
/*.failureHandler(((request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("登陆失败!");
System.out.println(request.getAuthType());
out.flush();
out.close();
}));*/
.failureForwardUrl("/login.html")
//退出
.and()
.logout().deleteCookies()退出登陆,默认url为/logout
.logoutSuccessUrl("/login.html");//跳转到登陆页面
}
实现记住我功能
登陆页面改造,多加一行
<input name="remember-me" type="checkbox" value="true" />
内存方式
@Override
protected void configure(HttpSecurity http) throws Exception {
http.rememberMe()
.tokenValiditySeconds(10000000);
}
数据库方式
//用来实现记住我功能
@Autowired
HrService hrService;//注入登陆程序对象
@Autowired
DataSource dataSource;
@Autowired
PersistentTokenRepository persistentTokenRepository;
@Bean//记住我功能实现 ,前端也要
public PersistentTokenRepository getPersistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);//使用数据库
//jdbcTokenRepository.setCreateTableOnStartup(true);//第一次使用的时候创建数据库,以后直接注释掉
return jdbcTokenRepository;
//记住我功能实现
http.rememberMe()
//自定义登陆逻辑
.userDetailsService(hrService)
//设置储存仓库,可以使用数据库,可以使用内存
.tokenRepository(persistentTokenRepository)
//失效时间
.tokenValiditySeconds(10000000);
;
}
关闭浏览器再打开访问-说明已经登陆
自动加密
@Bean//创建加密对象,注册以后默认使用,不要需要手动调用
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
数据库登陆
准备好数据库连接文件
依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
放行xml文件-否则报错绑定异常
<!-- 如果不添加此节点mybatis的mapper.xml文件都会被漏掉。 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
配置文件:
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/vhr?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
type: com.alibaba.druid.pool.DruidDataSource
server:
port: 8091
实现UserDetails类
/**
* 这个类是用户类,实现了SpringSecurity的UserDetails接口,用来替代用户信息
*/
public class Hr implements UserDetails {
private Integer id;
private String name;
private String phone;
private String telephone;
private String address;
private Boolean enabled;
private String username;
private String password;
private String userface;
private String remark;
private List<Role> roles;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Hr hr = (Hr) o;
return Objects.equals(username, hr.username);
}
@Override
public int hashCode() {
return Objects.hash(username);
}
//用来获取用户所有权限,储存在SimpleGrantedAuthority,
//SimpleGrantedAuthority是SpringSecurity的权限基本单元
@Override
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>(roles.size());
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;//返回权限管理单元
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password == null ? null : password.trim();
}
public String getUserface() {
return userface;
}
public void setUserface(String userface) {
this.userface = userface == null ? null : userface.trim();
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark == null ? null : remark.trim();
}
}
实现数据库查询比较的方法
@Component
public class HrService implements UserDetailsService {
public static final Logger logger = LoggerFactory.getLogger(HrService.class);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
@Autowired
HrMapper hrMapper;
@Autowired
HrRoleMapper hrRoleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
/**
*pringSecurity底层会调用DaoAuthenticationProvider类中的
* additionalAuthenticationChecks(UserDetails userDetails,
* UsernamePasswordAuthenticationToken authentication)方法,
* userDetails参数是loadUserByUsername方法返回的用户主体,
* authentication参数就是前台输入的用户名和密码的封装。
* 也就是说security底层会将从数据库中查询到的用户信息和前台输入的用户信息进行对比,
* 如果密码不匹配则抛出异常。我们只需要提供用户信息即可,
*/
Hr hr = hrMapper.loadUserByUsername(username);
if (null == hr) throw new UsernameNotFoundException("用户名不存在");
logger.info(hr.getUsername()+"登陆成功!时间:" + format.format(new Date()));
hr.setRoles(hrMapper.getHrRolesById(hr.getId()));
return hr;
}
}
配置文件中使用数据库自定义逻辑
@Autowired
HrService hrService;
//告诉系统不加密
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//用户授权
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/*auth.inMemoryAuthentication().//内存保存用户授权
withUser("lianlei").password("123").roles("admin")
.and()
.withUser("liubeibie").password("123").roles("admin");*/
auth.userDetailsService(hrService);//使用自定义登陆逻辑
}
登陆异常处理
//没有认证,返回消息
http.authenticationEntryPoint(((request, response, authException) -> {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("登陆失败!");
System.out.println(request.getAuthType());
out.flush();
out.close();
}))
// .accessDeniedHandler()
.accessDeniedPage("/accessDeny.html")//拒绝访问,跳转页面
登陆失败页面
处理器替代-根据访问路径判断用户是否有权限进行访问
需要重写两个处理器
MyCustomFilterInvocationSecurityMetadataSource 和 MyCustomUrlDecisionManager
其中第一个是通过访问路径查询需要的角色信息
第二个是把需要的角色信息和登陆用户的角色信息进行比对,有权限就通过,否则抛出异常
代码
/**
* @author Mr.Lian
* @create 2021-12-05 20:13
* 根据访问的url确定需要的权限
**/
@Component
public class MyCustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
MenuService menuService;
AntPathMatcher antPathMatcher = new AntPathMatcher();//进行匹配的对象,类似于正则匹配
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
// 获取请求的路径
String requestUrl = ((FilterInvocation) object).getRequestUrl();
//获取数据库当中所有请求路径, 集成reids//TODO
List<Menu> menus = menuService.getAllMenusWithRole();
for (Menu menu : menus) {
if (antPathMatcher.match(menu.getUrl() , requestUrl)){
List<Role> roles = menu.getRoles();
String[] role = new String[roles.size()];
for (int i = 0; i < roles.size(); i++) {
role[i] = roles.get(i).getName();
}
return SecurityConfig.createList(role);//返回所需要的角色信息
}
}
//如果不需要任何角色, 返回一个特定的标致
return SecurityConfig.createList(ConstantParam.NO_ROLE);
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return false;
}
}
/**
* @author Mr.Lian
* @create 2021-12-05 20:08
* 通过用户和访问的url路径判断是否有权限进行访问
**/
@Component
public class MyCustomUrlDecisionManager implements AccessDecisionManager {
/**
*
* @param authentication 此时登陆的用户
* @param object
* @param configAttributes 需要的权限
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
for (ConfigAttribute configAttribute : configAttributes) {
//需要的角色
String needRole = configAttribute.getAttribute();
if (ConstantParam.NO_ROLE == needRole){
//判断是否登陆,没有登陆,抛出异常
if (authentication instanceof AnonymousAuthenticationToken){
throw new AccessDeniedException("尚未登录,请登录!");
}else return;
}
//从用户信息中查找所带有的权限 逐个比较,有权限就通过(结束方法),否则抛出异常
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
//有权限就通过
if (authority.getAuthority().equals(needRole)) return;
}
}
//没有权限访问
throw new AccessDeniedException("权限不足");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return false;
}
@Override
public boolean supports(Class<?> clazz) {
return false;
}
}
配置文件当中进行注册。
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
HrService hrService;
//告诉系统不加密
@Autowired
MyCustomFilterInvocationSecurityMetadataSource myCustomFilterInvocationSecurityMetadataSource;
@Autowired
MyCustomUrlDecisionManager myCustomUrlDecisionManager;
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//用户授权
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/*auth.inMemoryAuthentication().//内存保存用户授权
withUser("lianlei").password("123").roles("admin")
.and()
.withUser("liubeibie").password("123").roles("admin");*/
auth.userDetailsService(hrService);//使用自定义登陆逻辑
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//注册自己的登陆过滤器 通过访问的路径和用户权限确定是否有角色登陆
http.authorizeRequests().
withObjectPostProcessor((new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(myCustomUrlDecisionManager);
object.setSecurityMetadataSource(myCustomFilterInvocationSecurityMetadataSource);
return object;
}
}));
注意:要在配置文件中放行
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/accessDeny.html","/login.html","/js/**","/css/**");
}
否则就会导致登陆页面无法打开
没有权限访问的时候,会提示限制登陆
添加验证码的登陆方法
首先在前端添加验证码位置以及刷新验证码方法
参考链接:链接
<div class="layui-form-item" style="margin-top:20px">
<label class="layadmin-user-login-icon layui-icon" style="z-index: 3"></label>
<input type="tel" id="verify_input" class="layui-input" lay-verify="required" autocomplete="off" placeholder="请输入验证码" maxlength="4" style="width: 40%;position:absolute;">
<a href="javascript:void(0);">
<img id="imgVerify" src="/verifyCode" alt="更换验证码" style="float: right;width: 40%;" onclick="getVerify(this);">
</a>
</div>
验证码生成器:
* 生成验证码的工具类
*/
public class VerificationCode {
private int width = 100;// 生成验证码图片的宽度
private int height = 30;// 生成验证码图片的高度
private String[] fontNames = { "宋体", "楷体", "隶书", "微软雅黑" };
private Color bgColor = new Color(255, 255, 255);// 定义验证码图片的背景颜色为白色
private Random random = new Random();
private String codes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private String text;// 记录随机字符串
/**
* 获取一个随意颜色
*
* @return
*/
private Color randomColor() {
int red = random.nextInt(150);
int green = random.nextInt(150);
int blue = random.nextInt(150);
return new Color(red, green, blue);
}
/**
* 获取一个随机字体
*
* @return
*/
private Font randomFont() {
String name = fontNames[random.nextInt(fontNames.length)];
int style = random.nextInt(4);
int size = random.nextInt(5) + 24;
return new Font(name, style, size);
}
/**
* 获取一个随机字符
*
* @return
*/
private char randomChar() {
return codes.charAt(random.nextInt(codes.length()));
}
/**
* 创建一个空白的BufferedImage对象
*
* @return
*/
private BufferedImage createImage() {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) image.getGraphics();
g2.setColor(bgColor);// 设置验证码图片的背景颜色
g2.fillRect(0, 0, width, height);
return image;
}
public BufferedImage getImage() {
BufferedImage image = createImage();
Graphics2D g2 = (Graphics2D) image.getGraphics();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 4; i++) {
String s = randomChar() + "";
sb.append(s);
g2.setColor(randomColor());
g2.setFont(randomFont());
float x = i * width * 1.0f / 4;
g2.drawString(s, x, height - 8);
}
this.text = sb.toString();
drawLine(image);
return image;
}
/**
* 绘制干扰线
*
* @param image
*/
private void drawLine(BufferedImage image) {
Graphics2D g2 = (Graphics2D) image.getGraphics();
int num = 5;
for (int i = 0; i < num; i++) {
int x1 = random.nextInt(width);
int y1 = random.nextInt(height);
int x2 = random.nextInt(width);
int y2 = random.nextInt(height);
g2.setColor(randomColor());
g2.setStroke(new BasicStroke(1.5f));
g2.drawLine(x1, y1, x2, y2);
}
}
public String getText() {
return text;
}
public static void output(BufferedImage image, OutputStream out) throws IOException {
ImageIO.write(image, "JPEG", out);
}
}
验证码访问路径,不必要忘了放行
@GetMapping("/verifyCode")
public void verifyCode(HttpServletRequest request, HttpServletResponse resp) throws IOException {
VerificationCode code = new VerificationCode();
BufferedImage image = code.getImage();
String text = code.getText();
HttpSession session = request.getSession(true);
session.setAttribute("verify_code", text);//储存在session中
VerificationCode.output(image,resp.getOutputStream());//输出验证码图片流
}
放行
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/accessDeny.html","/login.html","/js/**","/css/**", "/verifyCode");
}
此时已经可以这个显示了
添加后端验证功能-在登陆过滤器当中进行实现
写一个过滤器继承UsernamePasswordAuthenticationFilter类,重写attemptAuthentication方法,实现验证码检查逻辑,并且实现了自定义登陆逻辑。
代码:
public class MyLoginFilter extends UsernamePasswordAuthenticationFilter {
@Autowired
SessionRegistry sessionRegistry;
@Override//用户登陆信息验证
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {//保证post请求
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String verify_code = (String) request.getSession().getAttribute("verify_code");
if (request.getContentType().contains(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().contains(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
Map<String, String> loginData = new HashMap<>();
try {
loginData = new ObjectMapper().readValue(request.getInputStream(), Map.class);//获取前端输入数据
} catch (IOException e) {
}finally {
String code = loginData.get("verify_input");
checkCode(request, response, code, verify_code);//验证码验证
}
String username = loginData.get(getUsernameParameter());//获取传入的用户名
String password = loginData.get(getPasswordParameter());//获取出人入的密码
if (username == null) {//空是否
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(//登陆令牌
username, password);
setDetails(request, authRequest);
Hr principal = new Hr();
principal.setUsername(username);
sessionRegistry.registerNewSession(request.getSession(true).getId(), principal);
return this.getAuthenticationManager().authenticate(authRequest);
} else {
checkCode(request ,response, request.getParameter("verify_input"), verify_code);
return super.attemptAuthentication(request, response);
}
}
//在过滤器链上校验验证码
public void checkCode(HttpServletRequest request, HttpServletResponse response ,String codeInput, String code){
if (null == codeInput || "".equals(codeInput) || !codeInput.equalsIgnoreCase(code)) {
logger.info("验证码不正确!");
throw new AuthenticationServiceException("验证码不正确");
}
}
}
1、首先登录请求肯定是 POST,如果不是 POST ,直接抛出异常,后面的也不处理了。
2. 因为要在这里处理验证码,所以第二步从 session 中把已经下发过的验证码的值拿出来。
3. 接下来通过 contentType 来判断当前请求是否通过 JSON 来传递参数,如果是通过 JSON 传递参
数,则按照 JSON 的方式解析,如果不是,则调用 super.attemptAuthentication 方法,进入父类
的处理逻辑中,也就是说,我们自定义的这个类,既支持 JSON 形式传递参数,也支持 key/value
形式传递参数。
4. 如果是 JSON 形式的数据,我们就通过读取 request 中的 I/O 流,将 JSON 映射到一个 Map 上。
5. 从 Map 中取出 code,先去判断验证码是否正确,如果验证码有错,则直接抛出异常。验证码的
判断逻辑,大家可以参考:松哥手把手教你给微人事添加登录验证码。
6. 接下来从 Map 中取出 username 和 password,构造 UsernamePasswordAuthenticationToken
对象并作校验
最后再配置文件当中使用自己的过滤器替换已有的过滤器
注册到容器
@Bean
SessionRegistry sessionRegistry(){
return new SessionRegistryImpl();
}
@Bean
MyLoginFilter myLoginFilter() throws Exception {
MyLoginFilter loginFilter = new MyLoginFilter();
loginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
response.sendRedirect("/main.html");
}
);
//登陆失败hander处理器
loginFilter.setAuthenticationFailureHandler((request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
RespBean respBean = RespBean.error(exception.getMessage());
if (exception instanceof LockedException) {
respBean.setMsg("账户被锁定,请联系管理员!");
} else if (exception instanceof CredentialsExpiredException) {
respBean.setMsg("密码过期,请联系管理员!");
} else if (exception instanceof AccountExpiredException) {
respBean.setMsg("账户过期,请联系管理员!");
} else if (exception instanceof DisabledException) {
respBean.setMsg("账户被禁用,请联系管理员!");
} else if (exception instanceof BadCredentialsException) {
respBean.setMsg("用户名或者密码输入错误,请重新输入!");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
);
loginFilter.setAuthenticationManager(authenticationManagerBean());
loginFilter.setFilterProcessesUrl("/doLogin");//过滤器拦截路径
return loginFilter;
}
替换
http.addFilterAt(myLoginFilter() , UsernamePasswordAuthenticationFilter.class);
注意
loginFilter.setAuthenticationManager(authenticationManagerBean());登陆管理器
loginFilter.setFilterProcessesUrl("/doLogin");//过滤器拦截路径
没有这两个,就无法完成过滤器注册。
至此,已经完成了Vhr的权限管理
注意
如果你通过SecurityContextHolder.getContext().getAuthentication().getPrincipal()获取的结果为null,那么可能说明protected void configure(HttpSecurity http) throws Exception 方法当中配置的顺序是有问题的,可以参考我的配置。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/accessDeny.html","/login.html", "/static/js/**","/css/**").permitAll()//登陆页面放行
.anyRequest().authenticated()//其他页面需要登陆
.withObjectPostProcessor((new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(myCustomUrlDecisionManager);
object.setSecurityMetadataSource(myCustomFilterInvocationSecurityMetadataSource);
return object;
}
}))
//在filter已经处理,这里会失效
.and()
.formLogin().loginProcessingUrl("/doLogin")//登陆路径
.loginPage("/login.html")//登陆页面
.successHandler(new MysuccessHander())//登陆成功处理器
.failureForwardUrl("/login.html")
//登陆异常处理
.and()
.exceptionHandling()
//没有认证
.authenticationEntryPoint(((request, response, authException) -> {
response.sendRedirect("/login.html");
/* response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write( "请先认证");
System.out.println(request.getAuthType());
out.flush();
out.close();*/
}))
// .accessDeniedHandler()
.accessDeniedPage("/accessDeny.html")
//退出
.and()
.logout().deleteCookies()退出登陆,默认url为/logout
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//TODO
String name = authentication.getName();
Hr details = (Hr) hrService.loadUserByUsername1(name);
redisTemplate.opsForList().remove("user", 1, details);
logger.info(details.getUsername()+"退出成功, 时间:" + format.format(new Date()));
response.sendRedirect("/login.html");
}
}).and().csrf().disable();//跳转到登陆页面
//自定义用户的登陆过滤器替换原来过滤器
/**
* 注册自己的登陆过滤器 这里替换之后,原来的过滤器就会失效,因此后面配置的用户登陆信息也会失效
*/
http.addFilterAt(myLoginFilter() , UsernamePasswordAuthenticationFilter.class);
//记住我功能
http.rememberMe()
.tokenValiditySeconds(10000000);
}
SpringSecurity 如何获取用户信息,两种方法
方法一
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
方法二(交给spring自动注入,也可以使用注解注入Authentication 对象)
*@GetMapping("hr/info")
public Hr getHr(Authentication authentication){
return ((Hr) authentication.getPrincipal());
}
SpringSecurity 如何修改当前已经登陆的用户信息(数据库+本地Authrntication)
//修改用户信息,如果是当前用户就更新SpringSecurity 当中的信息
@PutMapping("/hr/update")
public RespBean updateInfo(Hr hr, Authentication authentication){
//更新信息
//数据库
if (hrService.updateInfoById(hr)){
if (hr.getUsername() != null && hr.getUsername().equals(((Hr)authentication.getAuthorities()).getUsername())){
//说明更换新的当前用户信息 Authentication中的
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(hr, authentication.getCredentials(), authentication.getAuthorities()));
}
return RespBean.ok("修改成功!");
}else {
return RespBean.error("修改失败!");
}
}