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(在个人码云主页有)咱们一块儿探讨学习。
下一篇文章咱们实现用户的操做日志和异常日志功能