目錄
SpringSecurity權限管理系統實戰(zhàn)—一、項目簡介和開發(fā)環(huán)境準備
SpringSecurity權限管理系統實戰(zhàn)—二、日志、接口文檔等實現
SpringSecurity權限管理系統實戰(zhàn)—三、主要頁面及接口實現
SpringSecurity權限管理系統實戰(zhàn)—四、整合SpringSecurity(上)
SpringSecurity權限管理系統實戰(zhàn)—五、整合SpringSecurity(下)
SpringSecurity權限管理系統實戰(zhàn)—六、SpringSecurity整合jwt
SpringSecurity權限管理系統實戰(zhàn)—七、處理一些問題
SpringSecurity權限管理系統實戰(zhàn)—八、AOP記錄用戶、異常日志
SpringSecurity權限管理系統實戰(zhàn)—九、數據權限的配置
前言
在寫完上一篇文章之后,我又研究了很久。最終我發(fā)現似乎我們這個項目不太適合用jwt。layui不像vue那樣可以通過axios 全局設置token(或許是我因為我菜,不知道怎么設置,如果有小伙伴有好的辦法,歡迎留言告訴我)。
這里稍微介紹下前端怎么操作(vue為例),主要就是拿到token以后將其存儲在localstorage或者cookies中,再從localstorage或者cookies中拿到token設置全局的請求頭,就可以了。
但是前一篇文章關于jwt的內容是沒有問題的,正常的使用也是那樣的步驟。
具體內容
去除JWT
那么既然不打算再用jwt了,就要老老實實的回去用cookies和session。那么我們需要把我們的項目還原成實戰(zhàn)五結束時候的樣子。
這里不是很好解釋了,就是把與jwt相關的刪除就行了,需要修改的地方有MyAuthenticationSuccessHandler,JwtAuthenticationTokenFilter,SpringSecurityConfig三處,刪去有關jwt的內容即可。(不多費篇幅)
前端提示信息
我這里修改了一下登錄頁面,讓其能夠在登錄失敗時,給出提示信息
<!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 權 限 管 理 系 統 實 戰(zhàn)
</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>
后端也做了登錄失敗的處理器
/**
* @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是一個抽象的異常類,他的常見子類為
UsernameNotFoundException 用戶找不到
BadCredentialsException 壞的憑據
AccountStatusException 用戶狀態(tài)異常它包含如下子類
AccountExpiredException 賬戶過期
LockedException 賬戶鎖定
DisabledException 賬戶不可用
CredentialsExpiredException 證書過期
都是在用戶登錄時可能會遇到的異常
修改后完整的SpringSecurityConfig
/**
* @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/**");//放行靜態(tài)資源
}
/**
* 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());
}
}
遇到的問題
一、Springsecurity中的UsernameNotFoundException異常無法被正常捕獲
具體的解釋可以看這篇文章(非常詳細,包括解決方案)
簡而言之,就是我拋出了UsernameNotFoundException異常但是最后會被轉換為BadCredentialsException異常。我這里不多做介紹了,上面那篇文章說的非常詳細。
如何解決也請參照那篇文章。我所使用的是取巧的方法,就是直接拋出BadCredentialsException異常而不是UsernameNotFoundException異常。因為畢竟最后給出的提示信息是模糊的“用戶名或密碼錯誤”,而不是具體到哪個錯誤了。
二、無法統一處理filter中拋出的異常
這個問題主要是和驗證碼的攔截器有關,前端拿不到驗證碼錯誤的提示信息。這里我們可以不用攔截器來處理驗證碼,可以自定義一個login請求來避開這個問題。
這個問題也是原本的寫法問題吧,其實原本需要用拋這個異常,直接向頁面輸出提示信息就好了。
我在找處理方法時找到有兩種方法供大家參考
后敘
這篇文章有點亂,博主的文筆真的不太行,所以在描述一些問題的時候可能會有點難以理解。如果小伙伴們在學習過程中有什么問題,歡迎大家加我的qq(在我的碼云主頁有)我們一起探討學習。
下一篇文章我們實現用戶的操作日志和異常日志功能