前面我們了解了springboot與springsecurity的整合,也了解了springsecurity通過oauth2完成單點登錄。這一節我們將看一下springsecurity在前后端分離項目中的使用,也就是其對vue界面的保護和管理。
1、環境約束
- idea2018.1
- maven3.6.1
2、前提約束
- 了解springboot與springsecurity的整合
https://www.jianshu.com/p/8ec3cd99bd26
3、操作步驟
3.1、創建前端項目
- 創建vue項目,假設名稱為springsecurity-vue-ui
https://www.jianshu.com/p/644eb12a8174 - 在springsecurity-vue-ui/src文件夾下創建views文件夾
- 在springsecurity-vue-ui/src/views文件夾下創建index.vue
<template>
<div >
用戶名: <input type="text" v-model="loginForm.username" />
<br><br>
密碼: <input type="text" v-model="loginForm.password" />
<br><br>
<input type="button" value="登錄" @click="login('loginForm')"/>
</div>
</template>
<script>
import axios from 'axios'
axios.defaults.withCredentials = true //跨域
axios.defaults.timeout = 10000
axios.defaults.headers.post['Content-Type'] = 'application/x-www=form-urlencoded'
export default {
data () {
return {
loginForm: {
username: 'zhangli',
password: '123456',
}
}
},
methods:{
login: function (){
let _this = this;
axios.post("/login?username="+this.loginForm.username+"&password="+this.loginForm.password)
.then(function (response) {
if(response.data.status="200")
{
window.location.href="/show"
}
})
.catch(function (reason) {
console.log(reason);
})
}
}
}
</script>
- 修改springsecurity-vue-ui/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Index from '../views/index/index'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
// 配置路由地址
{
path: '/index',
name: 'Index',
component: Index
}
]
})
- 修改springsecurity-vue-ui/config/index.js中的proxyTable節點:
proxyTable: {
'/login': {
target: 'http://localhost:8082',
'^/login':'/login'
},
'/show': {
target: 'http://localhost:8082',
'^/show':'/show'
},
'/logout': {
target: 'http://localhost:8082',
'^/logout':'/logout'
}
}
- 下載包、打包以及啟動,執行以下命令:
cd springsecurity-vue-ui
# 下載包
cnpm install
# 打包
cnpm run build
# 啟動
cnpm run start
- 訪問http://localhost:8080/#/index,得到以下界面:
3.2、創建后端項目
- 創建一個springboot項目,假設名稱為springsecurity-vue-server,pom.xml內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>
<groupId>net.wanho</groupId>
<artifactId>springsecurity_vue_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springsecurity_vue_server</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
<repositories>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>
- 在application.yml中設置端口:
server:
port: 8082
- 在主啟動類下創建UserInfo.java以記錄用戶信息
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class UserInfo implements UserDetails {
private User user;
private List<Role> roles;
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (roles == null || roles.isEmpty()) {
return new ArrayList<>();
}
List<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
}
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@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 static class Role implements Serializable {
private long id;
private String name;
public Role(long id, String name) {
this.id = id;
this.name = name;
}
public Role() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class User implements Serializable {
private Long id;
private String username;
private String password;
public User(Long id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public User() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
}
- 在主啟動類下創建CorsConfig.java以完成跨域:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*")
.allowedHeaders("*")
.allowedOrigins("*");
}
}
- 在主啟動類下創建MyUserDetailService.java以獲取用戶信息,注意,我們這邊的用戶信息是寫死的。
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.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo.User user = new UserInfo.User();
user.setUsername("zhangli");
user.setPassword("123456");
List<UserInfo.Role> list = new ArrayList<>();
UserInfo.Role role = new UserInfo.Role(1, "USER");
list.add(role);
UserInfo userDetail = new UserInfo();
userDetail.setRoles(list);
userDetail.setUser(user);
if (userDetail == null) {
throw new UsernameNotFoundException("Not found username:" + username);
}
return userDetail;
}
}
- 在主啟動類下創建MyAuthenticationProvider.java以驅動MyUserDetailService.java
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collection;
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
@Resource
private UserDetailsService userDetailService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = authentication.getName();// 這個獲取表單輸入中返回的用戶名;
String password = (String) authentication.getCredentials();// 這個是表單中輸入的密碼;
UserInfo userInfo = (UserInfo) userDetailService.loadUserByUsername(userName); // 這里調用我們的自己寫的獲取用戶的方法;
if (userInfo == null) {
throw new BadCredentialsException("用戶名不存在");
}
if (!userInfo.getPassword().equals(password)) {
throw new BadCredentialsException("密碼不正確");
}
Collection<? extends GrantedAuthority> authorities = userInfo.getAuthorities();
return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
- 在主啟動類下創建WebSecurityConfig.java以設置web安全
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
MyAuthenticationProvider authenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
// 對登錄注冊要允許匿名訪問;
.antMatchers("/login").permitAll()
// 其他的路徑都要登錄之后具備USER角色
.antMatchers("/**").hasRole("USER")
//這里配置的loginProcessingUrl為頁面中對應表單的 action ,該請求為 post,並設置可匿名訪問
.and().formLogin().loginProcessingUrl("/login").permitAll()
//登錄成功后的返回結果
.successHandler(new AuthenticationSuccessHandlerImpl())
//登錄失敗后的返回結果
.failureHandler(new AuthenticationFailureHandlerImpl())
//這里配置的logoutUrl為登出接口,並設置可匿名訪問
.and().logout().logoutUrl("/logout").permitAll()
//登出后的返回結果
.logoutSuccessHandler(new LogoutSuccessHandlerImpl())
//這里配置的為當未登錄訪問受保護資源時,返回json,並且讓springsecurity自帶的登錄界面失效
.and().exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPointHandler());
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
//定義登陸成功返回信息
private class AuthenticationSuccessHandlerImpl extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
UserInfo userInfo = (UserInfo) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
PrintWriter out = response.getWriter();
out.write("{\"status\":\"200\",\"msg\":\"登錄成功\"}");
out.flush();
out.close();
}
}
//定義登出成功返回信息
private class LogoutSuccessHandlerImpl extends SimpleUrlLogoutSuccessHandler {
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("{\"status\":\"200\",\"msg\":\"登出成功\"}");
out.flush();
out.close();
}
}
//定義登陸失敗返回信息
private class AuthenticationFailureHandlerImpl extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("{\"status\":\"100\",\"msg\":\"請檢查用戶名、密碼或驗證碼是否正確\"}");
out.flush();
out.close();
}
}
public class AuthenticationEntryPointHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.sendRedirect("http://localhost:8080/#/index");
}
}
}
- 在主啟動下創建UserController.java以提供api
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/show")
public JSONObject show(){
JSONObject jsonObject = new JSONObject();
jsonObject.put("status","200");
jsonObject.put("data","this is the data");
return jsonObject;
}
}
- 啟動springboot
3.3、測試
- 訪問http://localhost:8080/#/index,點擊“登錄”,得到以下頁面
注意,這里本應該是個頁面,但作者只提供了api訪問的響應結果 - 打開一個新的標簽頁,訪問http://localhost:8080/#/logout,得到以下頁面
- 再次刷新之前的http://localhost:8080/show,則又重新顯示登錄頁面。
以上就是springsecurity對vue界面的保護和管理。