springboot+springsecurity實現前後端分離簡單實現!
1、前言部分
1。1、如何學習?
看springsecurtiy原理圖的時候以為灑灑水,結果自己動手做的時候一竅不通,所以一定不要眼高手低,實踐出真知!透過各種方式學習springsecurity,在B站、騰訊課堂、網易課堂、慕課網沒有springsecurity的前後端分離的教學影片,那我就去csdn去尋找springsecurity部落格,發現幾個問題:
要麼就是前後端不分離,要麼就是透過記憶體方式讀取資料,而不是透過資料庫的方式讀取資料,要麼就是大佬們給的程式碼不全、把程式碼講的太繞,關鍵部分沒有註釋。
講的例子不那麼通俗易懂,不利於新手的學習。
程式碼本身有bug,或者就沒有我想要實現的效果。
實在不行我又跑去github上找開源專案學習,github由於是外國網站,國內訪問速度有點慢!!那就用國內的gitee吧,gitee上的開源專案都是結合實戰專案的,程式碼邏輯也比較複雜,我對專案的業務邏輯沒什麼瞭解,感覺不適合我。我這一次選擇比較反人性的方式去學習,就是手撕原始碼和看官方文件。老實講,剛開始看原始碼和官方文件特別難受,並且看不進去,那些springsecurity的類還有介面名字又臭又長,這時我就下載原始碼,原始碼的註釋多的就像一本書,非常詳細且權威。
當然別指望看幾遍就能看懂,我看這些註釋、原始碼、部落格看了10幾遍甚至20幾遍才看懂,每次去看都有不同的收穫!!!
此文章截圖水平不高、理解為主、欣賞為輔!!內容有點多,每一步都有詳細解析,請耐心看完,看不懂可以多看幾遍。。
1。2、技術支援
jdk 1。8、springboot 2。3。4、mybatis-plus 3。4。1、mysql 5。5、springsecurity 5。3。4、springmvc、lombok簡化entity程式碼,不用你去寫get、set方法,全部自動生成、gson 2。8。2 將json物件轉化成json字串
1。3、預期實現效果圖
未登入時訪問指定資源, 返回未登入的json字串 , index是我在controller層寫的一個簡單介面,返回index字串
輸入賬號錯誤,返回使用者名稱錯誤的json字串 , 需說明一點,/login是springsecurity封裝好的介面,無須你在controller寫login介面,/logout也同理。
輸入密碼錯誤,返回密碼錯誤的json字串
登入成功, 返回登入成功的json字串並返回cookie
登入成功並且擁有許可權訪問指定資源, 返回資源相關資料的json字串
登入成功但無許可權訪問指定資源時,返回許可權不足的json字串
異地登入,返回異地登入,強制下線的json字串 , 測試的基礎是要在兩臺不同的機器上登入,然後訪問/index。
登出成功,返回登出成功的json字串並刪除cookie
2、核心部分
2。1、springsecurity原理解釋:
springsecurity最重要的兩個部分: authentication(認證) 和 authorization(授權)
認證: 就是判定你是什麼身份,管理員還是普通人
授權: 什麼樣的身份擁有什麼樣的權利。
簡單理解: 自定義配置登入成功、登陸失敗、登出成功目標結果類,並將其注入到springsecurity的配置檔案中。如何認證、授權交給AuthenticationManager去作
複雜理解:
(1)使用者發起表單登入請求後,首先進入
UsernamePasswordAuthenticationFilter
, 在 UsernamePasswordAuthenticationFilter中根據使用者輸入的使用者名稱、密碼構建了
UsernamePasswordAuthenticationToken
,並將其交給 AuthenticationManager 來進行認證處理。
AuthenticationManager 本身不包含認證邏輯,其核心是用來管理所有的
AuthenticationProvider
,透過交由合適的 AuthenticationProvider 來實現認證。
(2)下面跳轉到了
SelfAuthenticationProvider
,該類是 AuthenticationProvider 的實現類:你可以在該類的
Authentication authenticate(Authentication authentication)
自定義認證邏輯, 然後在該類中透過呼叫
UserDetails loadUserByUsername(account)
去獲取資料庫使用者資訊並驗證,然後建立
並將許可權、使用者個人資訊注入到其中 ,並透過
setAuthenticated(true)
設定為需要驗證。
(3) 至此認證資訊就被傳遞迴 UsernamePasswordAuthenticationFilter 中,在 UsernamePasswordAuthenticationFilter 的父類
AbstractAuthenticationProcessingFilter
的
doFilter()
中,會根據認證的成功或者失敗呼叫相應的 handler:所謂的handler就是我們注入到springsecurity配置檔案的handler。
2。2、踩坑集錦
訪問/login時必須要用post方法!, 訪問的引數名必須為username和password
訪問/logout時即可用post也可用get方法!
//springsecurity配置檔案中的hasRole(“”)不能以ROLE開頭,比如ROLE_USER就是錯的,springsecurity會預設幫我們加上,但資料庫的許可權欄位必須是ROLE_開頭,否則讀取不到
。antMatchers(“/index”)。hasRole(“USER”)
。antMatchers(“/hello”)。hasRole(“ADMIN”)
2。3、程式碼部分
pom依賴檔案
<!——轉換成json字串的工具——>
<!——springboot整合web操作7——>
<!——springsecurity——>
<!——mysql驅動——>
<!——lombok依賴——>
<!——mybatis-plus依賴——>
<!——springboot-自帶測試工具——>
Msg。java 自定義返回結果集,這個看個人的,怎麼開心怎麼來!
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Msg {
int code; //錯誤碼
String Message; //訊息提示
Map
//無權訪問
public static Msg denyAccess(String message){
Msg result=new Msg();
result。setCode(300);
result。setMessage(message);
return result;
}
//操作成功
public static Msg success(String message){
Msg result=new Msg();
result。setCode(200);
result。setMessage(message);
return result;
}
//客戶端操作失敗
public static Msg fail(String message){
Msg result=new Msg();
result。setCode(400);
result。setMessage(message);
return result;
}
public Msg add(String key,Object value){
this。data。put(key,value);
return this;
}
}
User。java ,此類是entity實體類
@Data
public class User implements Serializable {
private Integer id;
private String account;
private String password;
private String role;
}
UserMapper。java ,此介面繼承 BaseMapper
@Repository
public interface UserMapper extends BaseMapper
}
正常情況下要寫UserService。java介面,但是此文章只是用於演示效果,就沒書寫了
public interface UserService{
}
UserServiceImpl。java,使其實現 UserDetailsService介面, 從而去獲取資料庫使用者資訊,詳細解析請看註釋部分。
@Service
public class UserServiceImpl extends ServiceImpl
@Autowired
UserMapper userMapper;
//載入使用者
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//mybatis-plus幫我們寫好了sql語句,相當於 select * from user where account =‘${account}’
QueryWrapper
wrapper。eq(“account”,s);
User user=userMapper。selectOne(wrapper); //user即為查詢結果
if(user==null){
throw new UsernameNotFoundException(“使用者名稱錯誤!!”);
}
//獲取使用者許可權,並把其新增到GrantedAuthority中
List
GrantedAuthority grantedAuthority=new SimpleGrantedAuthority(user。getRole());
grantedAuthorities。add(grantedAuthority);
//方法的返回值要求返回UserDetails這個資料型別, UserDetails是介面,找它的實現類就好了
//new org。springframework。security。core。userdetails。User(String username,String password,Collection<? extends GrantedAuthority> authorities) 就是它的實現類
return new org。springframework。security。core。userdetails。User(s,user。getPassword(),grantedAuthorities);
}
}
UserController。java 就是普通的controller
@RestController
public class UserController {
@GetMapping(“index”)
public String index(){
return “index”;
}
@GetMapping(“hello”)
public String hello(){
return “hello”;
}
}
AuthenticationEnryPoint 。java 自定義未登入的處理邏輯
@Component
public class AuthenticationEnryPoint implements AuthenticationEntryPoint {
@Autowired
Gson gson;
//未登入時返回給前端資料
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
Msg result=Msg。fail(“需要登入!!”);
response。setContentType(“application/json;charset=utf-8”);
response。getWriter()。write(gson。toJson(result));
}
}
AuthenticationFailure。java 自定義登入失敗時的處理邏輯
//登入失敗返回給前端訊息
@Component
public class AuthenticationFailure implements AuthenticationFailureHandler{
@Autowired
Gson gson;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
Msg msg=null;
if(e instanceof UsernameNotFoundException){
msg=Msg。fail(e。getMessage());
}else if(e instanceof BadCredentialsException){
msg=Msg。fail(“密碼錯誤!!”);
}else {
msg=Msg。fail(e。getMessage());
}
//處理編碼方式,防止中文亂碼的情況
response。setContentType(“text/json;charset=utf-8”);
//返回給前臺
response。getWriter()。write(gson。toJson(msg));
}
}
AuthenticationSuccess。java 自定義登入成功時的處理邏輯
@Component
public class AuthenticationSuccess implements AuthenticationSuccessHandler{
@Autowired
Gson gson;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//登入成功時返回給前端的資料
Msg result=Msg。success(“登入成功!!!!!”);
response。setContentType(“application/json;charset=utf-8”);
response。getWriter()。write(gson。toJson(result));
}
}
AuthenticationLogout。java 自定義登出時的處理邏輯
@Component
public class AuthenticationLogout implements LogoutSuccessHandler{
@Autowired
Gson gson;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Msg result=Msg。success(“登出成功”);
response。setContentType(“application/json;charset=utf-8”);
response。getWriter()。write(gson。toJson(result));
}
}
AccessDeny。java 自定義無許可權訪問時的邏輯處理
//無權訪問
@Component
public class AccessDeny implements AccessDeniedHandler{
@Autowired
Gson gson;
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
Msg result= Msg。denyAccess(“無權訪問,need Authorities!!”);
response。setContentType(“application/json;charset=utf-8”);
response。getWriter()。write(gson。toJson(result));
}
}
SessionInformationExpiredStrategy。java 自定義異地登入、賬號下線時的邏輯處理
@Component
public class SessionInformationExpiredStrategy implements org。springframework。security。web。session。SessionInformationExpiredStrategy{
@Autowired
Gson gson;
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
Msg result= Msg。fail(“您的賬號在異地登入,建議修改密碼”);
HttpServletResponse response=event。getResponse();
response。setContentType(“application/json;charset=utf-8”);
response。getWriter()。write(gson。toJson(result));
}
}
SelfAuthenticationProvider。java 自定義認證邏輯處理
@Component
public class SelfAuthenticationProvider implements AuthenticationProvider{
@Autowired
UserServiceImpl userServiceImpl;
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String account= authentication。getName(); //獲取使用者名稱
String password= (String) authentication。getCredentials(); //獲取密碼
UserDetails userDetails= userServiceImpl。loadUserByUsername(account);
boolean checkPassword= bCryptPasswordEncoder。matches(password,userDetails。getPassword());
if(!checkPassword){
throw new BadCredentialsException(“密碼不正確,請重新登入!”);
}
return new UsernamePasswordAuthenticationToken(account,password,userDetails。getAuthorities());
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
SpringsecurityConfig。java是springsecurity的配置,詳細解析請看註釋!!
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //開啟許可權註解,預設是關閉的
public class SpringsecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
AuthenticationEnryPoint authenticationEnryPoint; //未登入
@Autowired
AuthenticationSuccess authenticationSuccess; //登入成功
@Autowired
AuthenticationFailure authenticationFailure; //登入失敗
@Autowired
AuthenticationLogout authenticationLogout; //登出
@Autowired
AccessDeny accessDeny; //無權訪問
@Autowired
SessionInformationExpiredStrategy sessionInformationExpiredStrategy; //檢測異地登入
@Autowired
SelfAuthenticationProvider selfAuthenticationProvider; //自定義認證邏輯處理
@Bean
public UserDetailsService userDetailsService() {
return new UserServiceImpl();
}
//加密方式
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
//認證
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth。authenticationProvider(selfAuthenticationProvider);
}
//授權
@Override
protected void configure(HttpSecurity http) throws Exception {
//cors()解決跨域問題,csrf()會與restful風格衝突,預設springsecurity是開啟的,所以要disable()關閉一下
http。cors()。and()。csrf()。disable();
// /index需要許可權為ROLE_USER才能訪問 /hello需要許可權為ROLE_ADMIN才能訪問
http。authorizeRequests()
。antMatchers(“/index”)。hasRole(“USER”)
。antMatchers(“/hello”)。hasRole(“ADMIN”)
。and()
。formLogin() //開啟登入
。permitAll() //允許所有人訪問
。successHandler(authenticationSuccess) // 登入成功邏輯處理
。failureHandler(authenticationFailure) // 登入失敗邏輯處理
。and()
。logout() //開啟登出
。permitAll() //允許所有人訪問
。logoutSuccessHandler(authenticationLogout) //登出邏輯處理
。deleteCookies(“JSESSIONID”) //刪除cookie
。and()。exceptionHandling()
。accessDeniedHandler(accessDeny) //許可權不足的時候的邏輯處理
。authenticationEntryPoint(authenticationEnryPoint) //未登入是的邏輯處理
。and()
。sessionManagement()
。maximumSessions(1) //最多隻能一個使用者登入一個賬號
。expiredSessionStrategy(sessionInformationExpiredStrategy) //異地登入的邏輯處理
;
}
}
application。yml配置檔案
server:
port: 80
spring:
datasource:
url: jdbc:mysql://localhost:3306/springsecurity_test?characterEncoding=utf8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com。mysql。cj。jdbc。Driver