SpringSecurity權限管理系統實戰—1、項目簡介和開發環境準備
SpringSecurity權限管理系統實戰—2、日誌、接口文檔等實現
SpringSecurity權限管理系統實戰—3、主要頁面及接口實現
SpringSecurity權限管理系統實戰—4、整合SpringSecurity(上)
SpringSecurity權限管理系統實戰—5、整合SpringSecurity(下)
SpringSecurity權限管理系統實戰—6、SpringSecurity整合jwt
SpringSecurity權限管理系統實戰—7、處理一些問題
SpringSecurity權限管理系統實戰—8、AOP 記錄用戶日誌、異常日誌css
在寫完上一篇文章以後,我又研究了好久。最終我發現彷佛咱們這個項目不太適合用jwt。layui不像vue那樣能夠經過axios 全局設置token(或許是我由於我菜,不知道怎麼設置,若是有小夥伴有好的辦法,歡迎留言告訴我)。
這裏稍微介紹下前端怎麼操做(vue爲例),主要就是拿到token之後將其存儲在localstorage或者cookies中,再從localstorage或者cookies中拿到token設置全局的請求頭,就能夠了。
可是前一篇文章關於jwt的內容是沒有問題的,正常的使用也是那樣的步驟。html
那麼既然不打算再用jwt了,就要老老實實的回去用cookies和session。那麼咱們須要把咱們的項目還原成實戰五結束時候的樣子。前端
這裏不是很好解釋了,就是把與jwt相關的刪除就好了,須要修改的地方有MyAuthenticationSuccessHandler,JwtAuthenticationTokenFilter,SpringSecurityConfig三處,刪去有關jwt的內容便可。(很少費篇幅)vue
我這裏修改了一下登陸頁面,讓其可以在登陸失敗時,給出提示信息java
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <title></title> <link rel="stylesheet" href="/PearAdmin/admin/css/pearForm.css" /> <link rel="stylesheet" href="/PearAdmin/component/layui/css/layui.css" /> <link rel="stylesheet" href="/PearAdmin/admin/css/pearButton.css" /> <link rel="stylesheet" href="/PearAdmin/assets/login.css" /> </head> <body background="PearAdmin/admin/images/background.svg" > <form class="layui-form" method="get"> <div class="layui-form-item"> <img class="logo" src="PearAdmin/admin/images/logo.png" /> <div class="title">M-S-P Admin</div> <div class="desc"> Spring Security 權 限 管 理 系 統 實 戰 </div> </div> <div class="layui-form-item"> <input id="username" name="username" placeholder="用 戶 名 : " type="text" hover class="layui-input" required lay-verify="username"/> </div> <div class="layui-form-item"> <input id="password" name="password" placeholder="密 碼 : " type="password" hover class="layui-input" required lay-verify="password"/> </div> <div class="layui-form-item"> <input id="captcha" name="captcha" placeholder="驗 證 碼:" type="text" hover class="layui-verify" style="border: 1px solid #dcdfe6;" required lay-verify="captcha"> <img id="captchaImg" src="/captcha" width="130px" height="44px" onclick="this.src=this.src+'?'+Math.random()" title="點擊刷新"/> </div> <div class="layui-form-item"> <input type="checkbox" id="rememberme" name="rememberme" title="記住密碼" lay-skin="primary" checked> </div> <div class="layui-form-item"> <button style="background-color: #5FB878!important;" class="pear-btn pear-btn-primary login" lay-submit lay-filter="formLogin"> 登 入 </button> </div> </form> <script src="/PearAdmin/component/layui/layui.js" charset="utf-8"></script> <script> layui.use(['form', 'element','jquery'], function() { var form = layui.form; var element = layui.element; var $ = layui.jquery; // $("body").on("click",".login",function(obj){ // location.href="/api/admin" // }) form.verify({ username: function(value) { if (value.length <= 0 ) { return '用戶名不能爲空'; } }, password: function (value) { if (value.length <= 0) { return '密碼不能爲空'; } }, captcha: function (value) { if (value.length <= 0) { return '驗證碼不能爲空'; } if (value.length !== 4) { return '請輸入正確格式的驗證碼'; } } }) form.on('submit(formLogin)', function() { $.ajax({ url:'/login', type:'post', dataType:'text', data:{ username:$('#username').val(), password:$('#password').val(), captcha:$('#captcha').val(), rememberme:$('#rememberme').val() }, success:function(result){ var restjson = JSON.parse(result) if (restjson.success) { // layui.data("token", { // key: "Authorization", // value: "Bearer "+ restjson.jwt // }); layer.msg(restjson.msg,{icon:1,time:1000},function () { location.href = "/"; }); }else { layer.msg(restjson.msg,{icon:2,time:1000},function () { $("#captchaImg").attr("src","/captcha" + "?" + Math.random()); }); return false; } } }) return false; }); }) </script> </body> </html>
後端也作了登陸失敗的處理器jquery
/** * @author codermy * @createTime 2020/8/2 */ @Component public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setCharacterEncoding("utf-8");//修改編碼格式 httpServletResponse.setContentType("application/json"); httpServletResponse.getWriter().write(JSON.toJSONString(Result.error().message(e.getMessage())));//返回信息 } }
AuthenticationFailureHandler是一個抽象的異常類,他的常見子類爲ios
UsernameNotFoundException 用戶找不到 BadCredentialsException 壞的憑據 AccountStatusException 用戶狀態異常它包含以下子類 AccountExpiredException 帳戶過時 LockedException 帳戶鎖定 DisabledException 帳戶不可用 CredentialsExpiredException 證書過時
都是在用戶登陸時可能會遇到的異常git
修改後完整的SpringSecurityConfiggithub
/** * @author codermy * @createTime 2020/7/15 */ @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsServiceImpl userDetailsService; @Autowired private VerifyCodeFilter verifyCodeFilter;//驗證碼攔截器 @Autowired MyAuthenticationSuccessHandler authenticationSuccessHandler;//登陸成功邏輯 @Autowired private MyAuthenticationFailureHandler authenticationFailureHandler;//登陸失敗邏輯 @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;//jwt攔截器 @Autowired private RestAuthenticationEntryPoint restAuthenticationEntryPoint;//無權限攔截器 @Autowired private RestfulAccessDeniedHandler accessDeniedHandler;// 無權訪問 JSON 格式的數據 @Override public void configure(WebSecurity web) throws Exception { web.ignoring() .antMatchers(HttpMethod.GET, "/swagger-resources/**", "/PearAdmin/**", "/**/*.html", "/**/*.css", "/**/*.js", "/swagger-ui.html", "/webjars/**", "/v2/**");//放行靜態資源 } /** * anyRequest | 匹配全部請求路徑 * access | SpringEl表達式結果爲true時能夠訪問 * anonymous | 匿名能夠訪問 * denyAll | 用戶不能訪問 * fullyAuthenticated | 用戶徹底認證能夠訪問(非remember-me下自動登陸) * hasAnyAuthority | 若是有參數,參數表示權限,則其中任何一個權限能夠訪問 * hasAnyRole | 若是有參數,參數表示角色,則其中任何一個角色能夠訪問 * hasAuthority | 若是有參數,參數表示權限,則其權限能夠訪問 * hasIpAddress | 若是有參數,參數表示IP地址,若是用戶IP和參數匹配,則能夠訪問 * hasRole | 若是有參數,參數表示角色,則其角色能夠訪問 * permitAll | 用戶能夠任意訪問 * rememberMe | 容許經過remember-me登陸的用戶訪問 * authenticated | 用戶登陸後可訪問 */ @Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class); http.csrf().disable()//關閉csrf // .sessionManagement()// 基於token,因此不須要session // .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // .and() .httpBasic().authenticationEntryPoint(restAuthenticationEntryPoint)//未登錄時返回 JSON 格式的數據給前端 .and() .authorizeRequests() .antMatchers("/captcha").permitAll()//任何人都能訪問這個請求 .anyRequest().authenticated() .and() .formLogin() .loginPage("/login.html")//登陸頁面 不設限訪問 .loginProcessingUrl("/login")//攔截的請求 .successHandler(authenticationSuccessHandler) // 登陸成功 .failureHandler(authenticationFailureHandler) // 登陸失敗 .permitAll() .and() .rememberMe().rememberMeParameter("rememberme") // 防止iframe 形成跨域 .and() .headers() .frameOptions() .disable() .and(); // 禁用緩存 http.headers().cacheControl(); // 添加JWT攔截器 // http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 無權訪問返回JSON 格式的數據 } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); } /** * 身份認證接口 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } }
具體的解釋能夠看這篇文章(很是詳細,包括解決方案)web
簡而言之,就是我拋出了UsernameNotFoundException異常可是最後會被轉換爲BadCredentialsException異常。我這裏很少作介紹了,上面那篇文章說的很是詳細。
如何解決也請參照那篇文章。我所使用的是取巧的方法,就是直接拋出BadCredentialsException異常而不是UsernameNotFoundException異常。由於畢竟最後給出的提示信息是模糊的「用戶名或密碼錯誤」,而不是具體到哪一個錯誤了。
這個問題主要是和驗證碼的攔截器有關,前端拿不到驗證碼錯誤的提示信息。這裏咱們能夠不用攔截器來處理驗證碼,能夠自定義一個login請求來避開這個問題。
這個問題也是本來的寫法問題吧,其實本來須要用拋這個異常,直接向頁面輸出提示信息就行了。
我在找處理方法時找到有兩種方法供你們參考
這篇文章有點亂,博主的文筆真的不太行,因此在描述一些問題的時候可能會有點難以理解。若是小夥伴們在學習過程當中有什麼問題,歡迎你們加個人qq(在個人碼雲主頁有)咱們一塊兒探討學習。
下一篇文章咱們實現用戶的操做日誌和異常日誌功能