前後端分離整合SpringSecurity
簡單的前後端分離整合SpringSecurity
推薦學習地址:https://blog.csdn.net/u012702547/article/details/79019510
在普通的專案中使用SpringSecurity比較簡單,但是在前後端分離的專案中使用SpringSecurity就比較複雜了
總體步驟:
-
建立簡易的資料庫(使用者表、許可權表等)
-
建立一個簡單的Vue登入頁面
-
編寫實體類
-
編寫登入的dao、service等
-
在Controller中編寫一個
@GetMapping("/login")
的控制器 -
重點來了
+ 在實體類中實現UserDetails
+ 編寫UserDetailsService
實現loadUserByUsername
對輸入的使用者名稱、密碼進行驗證
+ 編寫主配置類,配置密碼加密、登入、攔截、登出等功能
+ 編寫輔類,主要用於顯示許可權不足、是否登入、許可權等等一些列的json
資料
程式碼
- 資料庫(省略)
- 建立一個簡單的Vue登入頁面
<template>
<div class="login-div">
<div class="img"></div>
<el-form ref="loginForm" :model="form" :rules="rules" label-width="70px" class="login-box" >
<div class="form">
<h3 class="login-title">歡迎登入</h3>
<el-form-item label="賬號" prop="username">
<el-input
type="text"
placeholder="請輸入賬號"
v-model="form.username"
/>
</el-form-item>
<el-form-item label="密碼" prop="password">
<el-input
type="password"
placeholder="請輸入密碼"
v-model="form.password"
/>
</el-form-item>
<el-form-item class="yzm" label="驗證碼" prop="yzm">
<el-input type="text" placeholder="請輸入驗證碼" v-model="form.yzm" />
<div class="vc">
<img
:src="'http://127.0.0.1:8080/api/users/umsUser/captcha.jpg'"
alt=""
/>
<span> <a href="">看不清?</a></span>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" v-on:click="onSubmit('loginForm')"
>登入</el-button
>
</el-form-item>
</div>
</el-form>
<el-dialog title="溫馨提示" :visible.sync="dialogVisible" width="30%">
<span>請輸入賬號和密碼</span>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false"
>確 定</el-button
>
</span>
</el-dialog>
</div>
</template>
<script>
import qs from "qs";
export default {
name: "Login",
data() {
return {
form: {
username: "",
password: "",
yzm: "",
},
imgsrc: "",
// 表單驗證,需要在 el-form-item 元素中增加 prop 屬性
rules: {
username: [
{ required: true, message: "賬號不可為空", trigger: "blur" },
],
password: [
{ required: true, message: "密碼不可為空", trigger: "blur" },
],
yzm: [{ required: true, message: "驗證碼不能為空", trigger: "blur" }],
},
// 對話方塊顯示和隱藏
dialogVisible: false,
};
},
methods: {
onSubmit(formName) {
// 為表單繫結驗證功能
this.$refs[formName].validate((valid) => {
if (valid) {
//傳送登入請求
var form = new window.FormData();
form.append("username", "laotie");
let url =`/api/users/login?username=` +this.form.username +"&password=" +this.form.password;
var _this=this;
this.$http.post(url,qs.stringify(form),
{
//設定請求頭,否則後臺獲取不到資料
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
},
{}
)
.then(function (data) {
if (data.data.status == "success") {
_this.$message({
message: '登入成功,歡迎回來:'+_this.form.username,
type: 'success'
});
_this.$router.push("/about");
}else{
console.log('登入失敗');
this.$message.error('登入失敗');
}
});
}
});
},
},
};
</script>
<style lang="scss" scoped>
.login-box {
width: 400px;
padding: 35px 35px 15px 35px;
border-radius: 5px;
margin-top: 25px;
margin: 0 auto;
}
.login-title {
text-align: center;
margin: 8px auto;
color: #303133;
}
.login-div {
display: flex;
align-items: center;
position: relative;
top: 60px;
}
.img {
width: 50%;
height: 500px;
background-image: url("../assets/background.jpg");
background-size: 100% 100%;
}
.form {
position: relative;
right: 160px;
}
.vc {
margin-top: 5px;
}
</style>
- 編寫實體類(省略)
- 編寫登入的dao、service等就是簡單的根據使用者名稱查詢使用者資訊(省略)
- 在Controller中編寫一個
@GetMapping("/login")
的控制器
@GetMapping("/login")
public R login() {
return R.failed("尚未登入,請登入!");
}
-
開始配置SpringSecurity
- 在實體類中實現
UserDetails
public class UmsUser extends Model<UmsUser> implements UserDetails { ...... @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<GrantedAuthority> authorities = new ArrayList<>(); // for (Role role : roles) { // authorities.add(new SimpleGrantedAuthority(role.getName())); // } return authorities; } @Override public boolean isAccountNonExpired() { return false; } @Override public boolean isAccountNonLocked() { return false; } @Override public boolean isCredentialsNonExpired() { return false; } @Override public boolean isEnabled() { return false; } }
- 編寫
UserDetailsService
實現loadUserByUsername
對輸入的使用者名稱、密碼進行驗證
@Slf4j @Service public class SecurityUserDatilService implements UserDetailsService { @Resource private UmsUserService umsUserService; /** * 登入 * @param username * @return */ @Override public UserDetails loadUserByUsername(String username) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); //獲取驗證碼此處可以省略 Object captcha = request.getSession().getAttribute("captcha"); System.out.println("captcha = " + captcha); //輸出使用者名稱 System.out.println("username = " +username); //設定許可權 Collection<GrantedAuthority> authorities = new ArrayList<>(); //根據使用者名稱查詢使用者資訊 QueryWrapper<UmsUser> umsUserQueryWrapper = new QueryWrapper<>(); umsUserQueryWrapper.eq("username", username); UmsUser umsUser = this.umsUserService.getOne(umsUserQueryWrapper); //判斷使用者是否存在 if (umsUser == null) { System.out.println("使用者名稱不對" ); throw new UsernameNotFoundException("使用者名稱不對"); } //賦予登入許可權 authorities.add(new SimpleGrantedAuthority("ROLE_LOGIN")); return new User(username,umsUser.getPassword(),authorities); } }
- 重要:編寫主配置類,配置密碼加密、登入、攔截、登出等功能
package com.swj.securiy; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 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 public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource private SecurityUserDatilService securityUserDatilService; @Resource UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource; @Resource UrlAccessDecisionManager urlAccessDecisionManager; @Resource AuthenticationAccessDeniedHandler authenticationAccessDeniedHandler; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(securityUserDatilService).passwordEncoder(passwordEncoder()); } /** * 攔截器 * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers( "/static/**","/umsUser/login","/umsUser/captcha.jpg"); } /** * 對登入失敗返回指定的json格式的資料 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O o) { o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource); o.setAccessDecisionManager(urlAccessDecisionManager); return o; } }).and().formLogin().usernameParameter("username").passwordParameter("password").permitAll().failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { //登入失敗 httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); StringBuffer sb = new StringBuffer(); sb.append("{\"status\":\"error\",\"msg\":\""); if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) { sb.append("使用者名稱或密碼輸入錯誤,登入失敗!"); } else if (e instanceof DisabledException) { sb.append("賬戶被禁用,登入失敗,請聯絡管理員!"); } else { sb.append("登入失敗!"); } sb.append("\"}"); out.write(sb.toString()); out.flush(); out.close(); } }).successHandler(new AuthenticationSuccessHandler() { //登入成功 @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); ObjectMapper objectMapper = new ObjectMapper(); String s = "{\"status\":\"success\",\"msg\":\"恭喜你登入成功了\"}"; out.write(s); out.flush(); out.close(); } //設定登出、跨域等資訊 }).and().logout().permitAll().and().csrf().disable().exceptionHandling().accessDeniedHandler(authenticationAccessDeniedHandler); } /** * 密碼加密 * @return */ @Bean BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
-
編寫輔類,主要用於顯示許可權不足、是否登入、許可權等等一些列的
json
資料- AuthenticationAccessDeniedHandler
package com.swj.securiy; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @Component public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException { resp.setStatus(HttpServletResponse.SC_FORBIDDEN); resp.setCharacterEncoding("UTF-8"); PrintWriter out = resp.getWriter(); out.write("{\"status\":\"error\",\"msg\":\"許可權不足,請聯絡管理員!\"}"); out.flush(); out.close(); } }
- UrlFilterInvocationSecurityMetadataSource
package com.swj.securiy; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.stereotype.Component; import org.springframework.security.access.SecurityConfig; import javax.annotation.Resource; import java.util.Collection; @Component public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Override public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException { String requestUrl = ((FilterInvocation) o).getRequestUrl(); return SecurityConfig.createList("ROLE_LOGIN"); } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> aClass) { return true; } }
- UrlAccessDecisionManager
package com.swj.securiy; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; import java.util.Collection; import java.util.Iterator; @Component public class UrlAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, AuthenticationException { Iterator<ConfigAttribute> iterator = collection.iterator(); while (iterator.hasNext()) { ConfigAttribute ca = iterator.next(); //當前請求需要的許可權 String needRole = ca.getAttribute(); if ("ROLE_LOGIN".equals(needRole)) { if (authentication instanceof AnonymousAuthenticationToken) { throw new BadCredentialsException("未登入"); } 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 configAttribute) { return true; } @Override public boolean supports(Class<?> aClass) { return true; } }
- 在實體類中實現
到此結束
總結:其實程式碼沒有那麼複雜,可以對部門程式碼進行省略,比如輔助類個別的就可以對其省略,完全沒有必要寫,因為像UrlFilterInvocationSecurityMetadataSource,可以對程式碼進行簡化,在主配置中的登入成功、登入失敗返回的資料完全可以單獨提成兩個方法。SpringSecurity前後端分離還是很有必要學習一下的。
相關文章
- 再談前後端分離後端
- 前後端分離那些事後端
- 淺談前後端分離後端
- 前後端分離——使用OSS後端
- 【SpringSecurity系列3】基於Spring Webflux整合SpringSecurity實現前後端分離無狀態Rest API的許可權控制SpringGseWebUX後端RESTAPI
- 前後端分離後的前端時代後端前端
- 前後端分離後模組開發後端
- ???前後端分離模式的思考???後端模式
- 前後端分離——資料mock後端Mock
- vue前後端分離修改webpackVue後端Web
- 前後端分離,最佳實踐後端
- 淺談WEB前後端分離Web後端
- 什麼是前後端分離?後端
- 前後端分離實踐有感後端
- 從MVC到前後端分離MVC後端
- 前後端分離Ajax入門後端
- Django+Vue.js搭建前後端分離專案 web前後端分離專案實踐DjangoVue.js後端Web
- Flask前後端分離專案案例Flask後端
- 從部署上做到前後端分離後端
- 前後端分離的優缺點後端
- 實現前後端分離的心得後端
- 簡單的前後端分離 Cas後端
- Laravel 前後端分離 csrf 防護Laravel後端
- 實踐中的前後端分離後端
- 前後端分離之Ajax入門後端
- Cloudera Manager 前後端分離部署方法Cloud後端
- 前後端分離開發腳手架後端
- jQuery 前後端分離專案總結jQuery後端
- 用jQuery怎麼做到前後端分離jQuery後端
- node-vue前後端分離記錄Vue後端
- 前後端分離開發部署模式【轉】後端模式
- 前後端分離技術路線圖後端
- 前後端分離前端模擬資料後端前端
- Web前後端:如何分離,如何解耦?Web後端解耦
- 前後端分離的好處有哪些?後端
- 前後端分離專案實踐分析後端
- 前後端完全分離之API設計後端API
- 前後端分離之更好的mock你的後端api後端MockAPI