java前后端分离登录认证_SpringBoot Security前后端分离登录验证的实现

本文详细介绍了如何在SpringBoot项目中利用Security实现前后端分离的登录验证,包括配置HTTP安全设置、自定义登录失败及成功处理器,以返回JSON格式的数据。通过配置AuthenticationProvider、UserDetailsService、PasswordEncoder等,确保登录、退出和权限管理的JSON响应。文中还涉及到跨域和CSRF的处理,并提供了测试步骤和结果。
摘要由CSDN通过智能技术生成

最近一段时间在研究OAuth2的使用,想整个单点登录,从网上找了很多demo都没有实施成功,也许是因为压根就不懂OAuth2的原理导致。有那么几天,越来越没有头绪,又不能放弃,转过头来一想,OAuth2是在Security的基础上扩展的,对于Security自己都是一无所知,干脆就先研究一下Security吧,先把Security搭建起来,找找感觉。

说干就干,在现成的SpringBoot 2.1.4.RELEASE环境下,进行Security的使用。

简单的Security的使用就不说了,目前的项目是前后端分离的,登录成功或者失败返回的数据格式必须JSON形式的,未登录时也需要返回JSON格式的提示信息 ,退出时一样需要返回JSON格式的数据。授权先不管,先返回JSON格式的数据,这一个搞定,也研究了好几天,翻看了很多别人的经验,别人的经验有的看得懂,有的看不懂,关键时刻还需要自己研究呀。

下面,上代码:

第一步,在pom.xml中引入Security配置文件

org.springframework.boot

spring-boot-starter-security

第二步,增加Configuration配置文件

import java.io.PrintWriter;

import java.util.HashMap;

import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.http.HttpMethod;

import org.springframework.security.authentication.AuthenticationProvider;

import org.springframework.security.authentication.BadCredentialsException;

import org.springframework.security.authentication.DisabledException;

import org.springframework.security.authentication.dao.DaoAuthenticationProvider;

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.core.userdetails.UserDetailsService;

import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.fasterxml.jackson.databind.ObjectMapper;

/**

* 参考网址:

* https://blog.csdn.net/XlxfyzsFdblj/article/details/82083443

* https://blog.csdn.net/lizc_lizc/article/details/84059004

* https://blog.csdn.net/XlxfyzsFdblj/article/details/82084183

* https://blog.csdn.net/weixin_36451151/article/details/83868891

* 查找了很多文件,有用的还有有的,感谢他们的辛勤付出

* Security配置文件,项目启动时就加载了

* @author 程就人生

*

*/

@Configuration

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired

private MyPasswordEncoder myPasswordEncoder;

@Autowired

private UserDetailsService myCustomUserService;

@Autowired

private ObjectMapper objectMapper;

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.authenticationProvider(authenticationProvider())

.httpBasic()

//未登录时,进行json格式的提示,很喜欢这种写法,不用单独写一个又一个的类

.authenticationEntryPoint((request,response,authException) -> {

response.setContentType("application/json;charset=utf-8");

response.setStatus(HttpServletResponse.SC_FORBIDDEN);

PrintWriter out = response.getWriter();

Map map = new HashMap();

map.put("code",403);

map.put("message","未登录");

out.write(objectMapper.writeValueAsString(map));

out.flush();

out.close();

})

.and()

.authorizeRequests()

.anyRequest().authenticated() //必须授权才能范围

.and()

.formLogin() //使用自带的登录

.permitAll()

//登录失败,返回json

.failureHandler((request,response,ex) -> {

response.setContentType("application/json;charset=utf-8");

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

PrintWriter out = response.getWriter();

Map map = new HashMap();

map.put("code",401);

if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) {

map.put("message","用户名或密码错误");

} else if (ex instanceof DisabledException) {

map.put("message","账户被禁用");

} else {

map.put("message","登录失败!");

}

out.write(objectMapper.writeValueAsString(map));

out.flush();

out.close();

})

//登录成功,返回json

.successHandler((request,response,authentication) -> {

Map map = new HashMap();

map.put("code",200);

map.put("message","登录成功");

map.put("data",authentication);

response.setContentType("application/json;charset=utf-8");

PrintWriter out = response.getWriter();

out.write(objectMapper.writeValueAsString(map));

out.flush();

out.close();

})

.and()

.exceptionHandling()

//没有权限,返回json

.accessDeniedHandler((request,response,ex) -> {

response.setContentType("application/json;charset=utf-8");

response.setStatus(HttpServletResponse.SC_FORBIDDEN);

PrintWriter out = response.getWriter();

Map map = new HashMap();

map.put("code",403);

map.put("message", "权限不足");

out.write(objectMapper.writeValueAsString(map));

out.flush();

out.close();

})

.and()

.logout()

//退出成功,返回json

.logoutSuccessHandler((request,response,authentication) -> {

Map map = new HashMap();

map.put("code",200);

map.put("message","退出成功");

map.put("data",authentication);

response.setContentType("application/json;charset=utf-8");

PrintWriter out = response.getWriter();

out.write(objectMapper.writeValueAsString(map));

out.flush();

out.close();

})

.permitAll();

//开启跨域访问

http.cors().disable();

//开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误

http.csrf().disable();

}

@Override

public void configure(WebSecurity web) {

//对于在header里面增加token等类似情况,放行所有OPTIONS请求。

web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");

}

@Bean

public AuthenticationProvider authenticationProvider() {

DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();

//对默认的UserDetailsService进行覆盖

authenticationProvider.setUserDetailsService(myCustomUserService);

authenticationProvider.setPasswordEncoder(myPasswordEncoder);

return authenticationProvider;

}

}

第三步,实现UserDetailsService接口

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.Component;

/**

* 登录专用类

* 自定义类,实现了UserDetailsService接口,用户登录时调用的第一类

* @author 程就人生

*

*/

@Component

public class MyCustomUserService implements UserDetailsService {

/**

* 登陆验证时,通过username获取用户的所有权限信息

* 并返回UserDetails放到spring的全局缓存SecurityContextHolder中,以供授权器使用

*/

@Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

//在这里可以自己调用数据库,对username进行查询,看看在数据库中是否存在

MyUserDetails myUserDetail = new MyUserDetails();

myUserDetail.setUsername(username);

myUserDetail.setPassword("123456");

return myUserDetail;

}

}

说明:这个类,主要是用来接收登录传递过来的用户名,然后可以在这里扩展,查询该用户名在数据库中是否存在,不存在时,可以抛出异常。本测试为了演示,把数据写死了。

第四步,实现PasswordEncoder接口

import org.springframework.security.crypto.password.PasswordEncoder;

import org.springframework.stereotype.Component;

/**

* 自定义的密码加密方法,实现了PasswordEncoder接口

* @author 程就人生

*

*/

@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);

}

}

说明:这个类主要是对密码加密的处理,以及用户传递过来的密码和数据库密码(UserDetailsService中的密码)进行比对。

第五步,实现UserDetails接口

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.stereotype.Component;

/**

* 实现了UserDetails接口,只留必需的属性,也可添加自己需要的属性

* @author 程就人生

*

*/

@Component

public class MyUserDetails implements UserDetails {

/**

*

*/

private static final long serialVersionUID = 1L;

//登录用户名

private String username;

//登录密码

private String password;

private Collection extends GrantedAuthority> authorities;

public void setUsername(String username) {

this.username = username;

}

public void setPassword(String password) {

this.password = password;

}

public void setAuthorities(Collection extends GrantedAuthority> authorities) {

this.authorities = authorities;

}

@Override

public Collection extends GrantedAuthority> getAuthorities() {

return this.authorities;

}

@Override

public String getPassword() {

return this.password;

}

@Override

public String getUsername() {

return this.username;

}

@Override

public boolean isAccountNonExpired() {

return true;

}

@Override

public boolean isAccountNonLocked() {

return true;

}

@Override

public boolean isCredentialsNonExpired() {

return true;

}

@Override

public boolean isEnabled() {

return true;

}

}

说明:这个类是用来存储登录成功后的用户数据,登录成功后,可以使用下列代码获取:

MyUserDetails myUserDetails= (MyUserDetails) SecurityContextHolder.getContext().getAuthentication() .getPrincipal();

代码写完了,接下来需要测试一下,经过测试才能证明代码的有效性,先用浏览器吧。

第一步测试,未登录前访问index,页面直接重定向到默认的login页面了,测试接口OK。

a335158218edbb1f867ab19c2766d40b.png

图-1

第二步测试,登录login后,返回了json数据,测试结果OK。

bc00d49013f2a37c2b7fbf0b9cb1877e.png

图-2

第三步测试,访问index,返回输出的登录数据,测试结果OK。

d662c0b96e5afd08a972a553d279238f.png

图-3

第四步,访问logout,返回json数据,测试接口OK。

30ca08d5d925708cf5e76058a0ade6a6.png

图-4

第五步,用API POST测试,用这个工具模拟ajax请求,看请求结果如何,首先访问index,这个必须登录后才能访问。测试结果ok,返回了我们需要的JSON格式数据。

283a120b846a30534c5e55b6c101af1c.png

图-5

第六步,在登录模拟对话框,设置环境变量,以保持登录状态。

48276decdfc140ce3fb50a56ba1669f8.png

图-6

**第七步,登录测试,返回JSON格式的数据,测试结果OK。

161ee2096efa46d877945cea22f103ef.png

图-7

第八步,在返回到index测试窗口,发送请求,返回当前用户JSON格式的信息,测试结果OK。

aa0688d76fc7c8650f4c3c09a8efaf63.png

图-8

第九步,测试退出,返回JSON格式数据,测试结果OK

7d5d0e6c1e06c028f4afa107fcbd51d4.png

图-9

第十步,退出后,再访问index,出现问题,登录信息还在,LOOK!

117829bb7c3de1c78af02c3fc5a09940.png

图-10

把头部的header前面的勾去掉,也就是去掉cookie,这时正常了,原因很简单,在退出时,没有清除cookie,这个只能到正式的环境上去测了。API POST再怎么模拟还是和正式环境有区别的。

如果在API POST测试报403错误,那就需要把configuration配置文件里的

//开启跨域访问

http.cors().disable();

//开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误

http.csrf().disable();

到此这篇关于SpringBoot Security前后端分离登录验证的实现的文章就介绍到这了,更多相关SpringBoot Security登录验证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

下面是一个简单的 Spring Boot Security + Vue 的前后端分离登录接口示例: 1. 后端实现 首先,我们需要创建一个 Spring Boot 项目,并添加以下依赖: ```xml <dependencies> <!-- Spring Boot Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot JPA --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- MySQL Connector --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- JSON Web Token --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> </dependencies> ``` 接下来,我们需要创建一个 User 实体类和 UserRepository 接口,用于管理用户信息。这里我们使用 MySQL 数据库作为数据存储。 ```java @Entity @Table(name = "users") public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String username; @Column(nullable = false) private String password; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")); } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); } ``` 然后,我们需要创建一个 Security 配置类,用于配置 Spring Security。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserRepository userRepository; @Autowired private JwtTokenProvider jwtTokenProvider; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(username -> userRepository.findByUsername(username)) .passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() .and() .apply(new JwtConfigurer(jwtTokenProvider)); } } ``` 这个配置类中,我们使用了 BCryptPasswordEncoder 作为密码加密方式,并且使用了自定义的 UserDetailsService 来获取用户信息。此外,我们还配置了一些请求的权限,其中 /api/auth/** 接口是登录接口,我们允许所有人访问。 最后,我们需要创建一个 JwtTokenProvider 类,用于生成和验证 JSON Web Token。 ```java @Component public class JwtTokenProvider { private static final String SECRET_KEY = "my-secret-key"; private static final long EXPIRATION_TIME = 86400000L; // 1 day public String generateToken(User user) { Date now = new Date(); Date expiration = new Date(now.getTime() + EXPIRATION_TIME); return Jwts.builder() .setSubject(user.getUsername()) .setIssuedAt(now) .setExpiration(expiration) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public Authentication getAuthentication(String token) { UserDetails userDetails = new UserDetailsService() { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found"); } return user; } }.loadUserByUsername(getUsername(token)); return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); return true; } catch (JwtException | IllegalArgumentException e) { return false; } } public String getUsername(String token) { return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject(); } } ``` 在这个类中,我们定义了一个 SECRET_KEY 和 EXPIRATION_TIME 常量,分别用于生成 JSON Web Token 和设置 Token 的有效期。generateToken() 方法用于生成 Token,getAuthentication() 方法用于根据 Token 获取用户认证信息,validateToken() 方法用于验证 Token 的有效性,getUsername() 方法用于获取 Token 中的用户名。 最后,我们需要创建一个 JwtConfigurer 类,用于配置 Spring Security 的过滤器链。 ```java public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { private JwtTokenProvider jwtTokenProvider; public JwtConfigurer(JwtTokenProvider jwtTokenProvider) { this.jwtTokenProvider = jwtTokenProvider; } @Override public void configure(HttpSecurity http) throws Exception { JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider); http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); } } ``` 这个类中,我们创建了一个 JwtTokenFilter 过滤器,并将其添加到 Spring Security 的过滤器链中。 2. 前端实现 在前端,我们使用 Vue 框架来实现登录页面。首先,我们需要安装 axios 库,用于发送 HTTP 请求。 ```bash npm install --save axios ``` 然后,我们创建一个 Login.vue 组件,在其中添加一个表单,用于输入用户名和密码。 ```html <template> <div class="login"> <h1>Login</h1> <form> <div class="form-group"> <label for="username">Username:</label> <input type="text" class="form-control" id="username" v-model="username"> </div> <div class="form-group"> <label for="password">Password:</label> <input type="password" class="form-control" id="password" v-model="password"> </div> <button type="submit" class="btn btn-primary" @click.prevent="login">Submit</button> </form> </div> </template> <script> import axios from 'axios'; export default { name: 'Login', data() { return { username: '', password: '' }; }, methods: { login() { axios.post('/api/auth/login', { username: this.username, password: this.password }).then(response => { localStorage.setItem('access_token', response.data.token); this.$router.push('/'); }).catch(error => { console.error(error); }); } } }; </script> ``` 在这个组件中,我们使用了 axios 库来发送 POST 请求到 /api/auth/login 接口,并在登录成功后将 Token 存储到 localStorage 中,然后跳转到首页。 3. 完整示例 完整的代码示例可以在以下链接中找到: https://github.com/zhangxu-s/springboot-vue-login-example
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值