前後端分離 SpringBoot + SpringSecurity 許可權解決方案
阿新 • • 發佈:2019-01-08
一、前言
而公司這邊都是前後端分離鮮明的,前端不要接觸過多的業務邏輯,都由後端解決,基本思路是這樣的:
服務端通過 JSON字串,告訴前端使用者有沒有登入、認證,前端根據這些提示跳轉對應的登入頁、認證頁等。
二、程式碼
下面給個示例,該自上述的之前的程式碼
1.AjaxResponseBody 給前端JSON的格式
返回給前端的資料格式
package com.cun.security3.bean;
import java.io.Serializable;
public class AjaxResponseBody implements Serializable {
private String status;
private String msg;
private Object result;
private String jwtToken;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
public String getJwtToken() {
return jwtToken;
}
public void setJwtToken(String jwtToken) {
this .jwtToken = jwtToken;
}
}
2.AjaxAuthenticationEntryPoint 未登入
使用者沒有登入時返回給前端的資料
package com.cun.security3.config;
import com.alibaba.fastjson.JSON;
import com.cun.security3.bean.AjaxResponseBody;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("000");
responseBody.setMsg("Need Authorities!");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
}
3.AjaxAuthenticationFailureHandler 登入失敗
使用者登入失敗時返回給前端的資料
package com.cun.security3.config;
import com.alibaba.fastjson.JSON;
import com.cun.security3.bean.AjaxResponseBody;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("400");
responseBody.setMsg("Login Failure!");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
}
4.AjaxAuthenticationSuccessHandler 登入成功
使用者登入成功時返回給前端的資料
package com.cun.security3.config;
import com.alibaba.fastjson.JSON;
import com.cun.security3.bean.AjaxResponseBody;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("200");
responseBody.setMsg("Login Success!");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
}
5.AjaxAccessDeniedHandler 無權訪問
package com.cun.security3.config;
import com.alibaba.fastjson.JSON;
import com.cun.security3.bean.AjaxResponseBody;
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;
@Component
public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("300");
responseBody.setMsg("Need Authorities!");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
}
6.SpringSecurityConf 登入攔截全域性配置
package com.cun.security3.config;
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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
@Configuration
public class SpringSecurityConf extends WebSecurityConfigurerAdapter {
@Autowired
AjaxAuthenticationEntryPoint authenticationEntryPoint; // 未登陸時返回 JSON 格式的資料給前端(否則為 html)
@Autowired
AjaxAuthenticationSuccessHandler authenticationSuccessHandler; // 登入成功返回的 JSON 格式資料給前端(否則為 html)
@Autowired
AjaxAuthenticationFailureHandler authenticationFailureHandler; // 登入失敗返回的 JSON 格式資料給前端(否則為 html)
@Autowired
AjaxLogoutSuccessHandler logoutSuccessHandler; // 登出成功返回的 JSON 格式資料給前端(否則為 登入時的 html)
@Autowired
AjaxAccessDeniedHandler accessDeniedHandler; // 無權訪問返回的 JSON 格式資料給前端(否則為 403 html 頁面)
@Autowired
SelfAuthenticationProvider provider; // 自定義安全認證
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 加入自定義的安全認證
auth.authenticationProvider(provider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic().authenticationEntryPoint(authenticationEntryPoint)
.and()
.authorizeRequests()
.anyRequest()
.authenticated()// 其他 url 需要身份認證
.and()
.formLogin() //開啟登入
.successHandler(authenticationSuccessHandler) // 登入成功
.failureHandler(authenticationFailureHandler) // 登入失敗
.permitAll()
.and()
.logout()
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll();
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 無權訪問 JSON 格式的資料
}
}
7.SelfUserDetails 自定義 user 物件
package com.cun.security3.config;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
/**
* ① 定義 user 物件
*/
public class SelfUserDetails implements UserDetails, Serializable {
private String username;
private String password;
private Set<? extends GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
public void setAuthorities(Set<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
@Override
public String getPassword() { // 最重點Ⅰ
return this.password;
}
@Override
public String getUsername() { // 最重點Ⅱ
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
8.SelfUserDetailsService 使用者認證、許可權
package com.cun.security3.config;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
/**
* ② 根據 username 獲取資料庫 user 資訊
*/
@Component
public class SelfUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//構建使用者資訊的邏輯(取資料庫/LDAP等使用者資訊)
SelfUserDetails userInfo = new SelfUserDetails();
userInfo.setUsername(username); // 任意使用者名稱登入
Md5PasswordEncoder md5PasswordEncoder = new Md5PasswordEncoder();
String encodePassword = md5PasswordEncoder.encodePassword("123", username); // 模擬從資料庫中獲取的密碼原為 123
userInfo.setPassword(encodePassword);
Set authoritiesSet = new HashSet();
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN"); // 模擬從資料庫中獲取使用者角色
authoritiesSet.add(authority);
userInfo.setAuthorities(authoritiesSet);
return userInfo;
}
}
9.SelfAuthenticationProvider 前端互動
package com.cun.security3.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
@Component
public class SelfAuthenticationProvider implements AuthenticationProvider {
@Autowired
SelfUserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = (String) authentication.getPrincipal(); // 這個獲取表單輸入中返回的使用者名稱;
String password = (String) authentication.getCredentials(); // 這個是表單中輸入的密碼;
Md5PasswordEncoder md5PasswordEncoder = new Md5PasswordEncoder();
String encodePwd = md5PasswordEncoder.encodePassword(password, userName);
UserDetails userInfo = userDetailsService.loadUserByUsername(userName);
if (!userInfo.getPassword().equals(encodePwd)) {
throw new BadCredentialsException("使用者名稱密碼不正確,請重新登陸!");
}
return new UsernamePasswordAuthenticationToken(userName, password, userInfo.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
三、使用 postman 測試 ajax
1.未登入
2.登入失敗
3.登入成功
4.登出成功
四、其他
溫馨提示:session+cookie 不安全 ~
上述略有前後端分離的影子,真正前後端開發還要引入 JWT,有空再敘