如果不是前后端分离项目,使用SpringSecurity做登录功能会很省心,只要简单的几项配置,便可以轻松完成登录成功失败的处理,当访问需要认证的页面时,可以自动重定向到登录页面。但是前后端分离的项目就不一样了,不能直接由后台处理,而是要向前端返回相应的json提示。

在本例的介绍中,主要解决了以下几个问题:

1.使用json格式数据进行登录。
2.登录成功或失败处理返回json提示。
3.未登录时访问需要认证的url时,返回json提示。
4.session过期时返回json提示。

一、引入security依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

二、编写配置文件

package com.hanstrovsky.config;
... /**
* @author Hanstrovsky
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // security默认不支持注解的方式的权限控制,加上这个注解开启
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private final MyUserDetailsService myUserDetailsService; private final MyPasswordEncoder myPasswordEncoder; public WebSecurityConfig(MyUserDetailsService myUserDetailsService, MyPasswordEncoder myPasswordEncoder) {
this.myUserDetailsService = myUserDetailsService;
this.myPasswordEncoder = myPasswordEncoder;
} @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 定义加密解密方式
auth.userDetailsService(myUserDetailsService).passwordEncoder(myPasswordEncoder);
} @Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.httpBasic()
// 访问需要认证的url,进行json提示
.and().exceptionHandling()
.authenticationEntryPoint((req, resp, e) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
FrontResult frontResult = FrontResult.init(FrontResult.LOGIN, "未登录或登录超时!");
out.write(new ObjectMapper().writeValueAsString(frontResult));
out.flush();
out.close();
})
.and()
.authorizeRequests()
.anyRequest().authenticated()// 必须认证之后才能访问 .and()
.formLogin()// 表单登录
.permitAll() // 和表单登录相关的接口统统都直接通过 .and()
.logout().deleteCookies("JSESSIONID")// 注销登录,删除cookie
// 自定义注销成功,返回json
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
FrontResult frontResult = FrontResult.init(FrontResult.SUCCEED, "注销成功!");
out.write(new ObjectMapper().writeValueAsString(frontResult));
out.flush();
out.close();
}
}) .and()
// session 超时返回json提示
.sessionManagement()
.maximumSessions(5).maxSessionsPreventsLogin(true)// 同一用户最大同时在线数量5个,超出后阻止登录
// session 超时返回json提示
.expiredSessionStrategy(new SessionInformationExpiredStrategy() {
@Override
public void onExpiredSessionDetected(
SessionInformationExpiredEvent sessionInformationExpiredEvent) throws IOException, ServletException {
HttpServletResponse resp = sessionInformationExpiredEvent.getResponse();
// 返回提示
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
FrontResult frontResult = FrontResult.init(FrontResult.LOGIN, "登录超时!");
out.write(new ObjectMapper().writeValueAsString(frontResult));
out.flush();
out.close();
}
});
//用重写的Filter替换掉原有的UsernamePasswordAuthenticationFilter
http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);} //注册自定义的UsernamePasswordAuthenticationFilter,使用json格式数据登录
@Bean
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
// 自定义登录成功或失败 返回json提示
filter.setAuthenticationSuccessHandler((req, resp, authentication) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
FrontResult frontResult = FrontResult.init(FrontResult.SUCCEED, "登录成功!");
out.write(new ObjectMapper().writeValueAsString(frontResult));
out.flush();
out.close();
});
filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
String errorMessage = "登录失败";
FrontResult frontResult = FrontResult.init(FrontResult.FAILED, errorMessage);
out.write(new ObjectMapper().writeValueAsString(frontResult));
out.flush();
out.close();
}
});
filter.setFilterProcessesUrl("/user/login");
//重用WebSecurityConfigurerAdapter配置的AuthenticationManager,不然要自己组装AuthenticationManager
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
}

三、实现Json登录的处理逻辑

security默认提供了Basic和表单两种登录方式,不支持Json格式的数据,需要对处理登录的过滤器进行修改。这里,我们重写了UsernamePasswordAuthenticationFilter的attemptAuthentication方法。

package com.hanstrovsky.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map; /**
* 自定义过滤器,重写 attemptAuthentication方法,实现使用json格式的数据进行登录
*
* @author Hanstrovsky
*/
@Slf4j
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
ObjectMapper mapper = new ObjectMapper();
UsernamePasswordAuthenticationToken authRequest = null;
try (InputStream is = request.getInputStream()) {
Map<String, String> authenticationBean = mapper.readValue(is, Map.class);
String username = authenticationBean.get("username");
String password = authenticationBean.get("password");
authRequest = new UsernamePasswordAuthenticationToken(
username, password);
} catch (IOException e) {
e.printStackTrace();
authRequest = new UsernamePasswordAuthenticationToken(
"", "");
}
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest); } else {
// 保留原来的方法
return super.attemptAuthentication(request, response);
}
}
}

四、实现UserDetailsService接口

这个接口是用来提供用户名和密码的,可以通过查询数据库获取用户。本例直接在代码中写死。

package com.hanstrovsky.service;

import com.hanstrovsky.entity.MyUserDetails;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Repository; /**
* @author Hanstrovsky
*/
@Repository
public class MyUserDetailsService implements UserDetailsService { @Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { // 可以在此处自定义从数据库查询用户
MyUserDetails myUserDetail = new MyUserDetails();
myUserDetail.setUsername(username);
myUserDetail.setPassword("123456");
return myUserDetail;
}
}

五、实现PasswordEncoder接口

自定义密码的加密方式。

package com.hanstrovsky.util;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component; /**
* 自定义的密码加密方法,实现了PasswordEncoder接口
*
* @author Hanstrovsky
*/
@Component
public class MyPasswordEncoder implements PasswordEncoder { @Override
public String encode(CharSequence charSequence) {
//加密方法可以根据自己的需要修改
return charSequence.toString();
} @Override
public boolean matches(CharSequence charSequence, String s) {
return encode(charSequence).equals(s);
}
}

六、实现UserDetails接口

这个类是用来存储登录成功后的用户数据,security提供了直接获取用户信息的接口

package com.hanstrovsky.entity;

import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component; import java.util.Collection; /**
* 实现UserDetails,可自定义添加更多属性
*
* @author Hanstrovsky
*/
@Getter
@Setter
@Component
public class MyUserDetails implements UserDetails { //登录用户名
private String username;
//登录密码
private String password; private Collection<? extends GrantedAuthority> authorities; @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
} private boolean accountNonExpired = true; private boolean accountNonLocked = true; private boolean credentialsNonExpired = true; private boolean enabled = true;
}

以上,便可以实现前后端分离项目基本的登录功能。

如何使用Spring Securiry实现前后端分离项目的登录功能的更多相关文章

  1. 喜大普奔,两个开源的 Spring Boot + Vue 前后端分离项目可以在线体验了

    折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...

  2. 两个开源的 Spring Boot + Vue 前后端分离项目

    折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...

  3. 基于spring security 实现前后端分离项目权限控制

    前后端分离的项目,前端有菜单(menu),后端有API(backendApi),一个menu对应的页面有N个API接口来支持,本文介绍如何基于spring security实现前后端的同步权限控制. ...

  4. Window下,前后端分离项目,登录权限验证中的,Redis相关操作

    [1]官网下载Redis(解压版) https://redis.io/download [2]切换到目录下打开DOS,执行指令启动Redis redis-server.exe redis.window ...

  5. 使用 Nginx 部署前后端分离项目,解决跨域问题

    前后端分离这个问题其实松哥和大家聊过很多了,上周松哥把自己的两个开源项目部署在服务器上以帮助大家可以快速在线预览(喜大普奔,两个开源的 Spring Boot + Vue 前后端分离项目可以在线体验了 ...

  6. Springboot+vue前后端分离项目,poi导出excel提供用户下载的解决方案

    因为我们做的是前后端分离项目 无法采用response.write直接将文件流写出 我们采用阿里云oss 进行保存 再返回的结果对象里面保存我们的文件地址 废话不多说,上代码 Springboot 第 ...

  7. 《Spring Boot 入门及前后端分离项目实践》系列介绍

    课程计划 课程地址点这里 本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 个部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 项目实践开发 ...

  8. 《Spring Boot 入门及前后端分离项目实践》目录

    开篇词:SpringBoot入门及前后端分离项目实践导读 第02课:快速认识 Spring Boot 技术栈 第03课:开发环境搭建 第04课:快速构建 Spring Boot 应用 第05课:Spr ...

  9. Spring Boot + Vue 前后端分离开发,前端网络请求封装与配置

    前端网络访问,主流方案就是 Ajax,Vue 也不例外,在 Vue2.0 之前,网络访问较多的采用 vue-resources,Vue2.0 之后,官方不再建议使用 vue-resources ,这个 ...

随机推荐

  1. Linux:shift 命令可以将参数依次向左移动一个位置

    在脚本中,命令行参数可以依据其在命令行中的位置来访问.第一个参数是 $1 ,第二个参数 是 $2 ,以此类推. 下面的语句可以显示出前3个命令行参数: echo $1 $2 $3 更为常见的处理方式是 ...

  2. Java基础题记录

    1. 装箱和拆箱 装箱:自动将基本数据类型转换为包装器类型即引用数据类型 拆箱:将包装器类型转换为基本数据类型 2. Java的8中基本数据类型 关键字 字节数 范围 默认值 boolelan 1by ...

  3. # G++出现cannot open output file … : Permission denied问题

    G++出现cannot open output file - : Permission denied问题 这是因为之前的编译运行程序没有退出,导致下一次编译运行无法进行,这应该是命令行下运行才可能出现 ...

  4. django CBV装饰器 自定义django中间件 csrf跨站请求伪造 auth认证模块

    CBV加装饰器 第一种 @method_decorator(装饰器) 加在get上 第二种 @method_decorator(login_auth,name='get') 加在类上 第三种 @met ...

  5. 记_JavaEE框架应用开发期末设计(一)

    日志 工作者:Black_YeJing 工作目标:实现卖家dao层的商品的增删改查(只能对自己发布的进行增删改查). 工作进程追踪: ①创建了Shop类(卖家类) ②创建了ShopDao的接口里面编写 ...

  6. Spectral Norm Regularization for Improving the Generalizability of Deep Learning论文笔记

    Spectral Norm Regularization for Improving the Generalizability of Deep Learning论文笔记 2018年12月03日 00: ...

  7. 怎样设置 MySQL 远程连接

    允许用户 root 在 任何IP 上都可以远程连接 所有 mysql数据库 并具有操作数据库的 所有权限, 密码为: myPassword mysql -u root -p grant all PRI ...

  8. 怎样理解 Vue 中的 v-if 和 v-show ?

    1. v-if 实现了真正的 条件渲染, 条件为真时, 节点被创建, 相应的监听函数也会生效, 条件为假时, 节点被销毁, 触发事件监听函数不会生效. 而 v-show 只是使用了 display:n ...

  9. JS基础_流程控制语句

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  10. Java并发编程——线程的基本概念和创建

    一.线程的基本概念: 1.什么是进程.什么是是线程.多线程? 进程:一个正在运行的程序(程序进入内存运行就变成了一个进程).比如QQ程序就是一个进程. 线程:线程是进程中的一个执行单元,负责当前进程中 ...