项目日记(在线办公项目)day2021/02/24

项目日记day0224

一、通过用户登录失败的异常信息来给用户进行提示

登陆失败后,我们一般会通过不同的失败信息来提示用户,但目前如果登陆失败,无论是账户名错误或者密码错误都只会提示登陆失败。

SpringScurity默认将登陆失败的异常封装到session对象中。

我们知道,session对象是以键值对的方式存在的,现在我们来探究一下,登陆失败后的session中的全部的键。

在这里插入图片描述

我们发现登陆失败后,session对象中存在有一个以SPRING_SECURITY_LAST_EXCEPTION为key的键值对。

现在我们测试一下,通过不同的登陆失败的方式,SPRING_SECURITY_LAST_EXCEPTION中会存些什么异常。

经过测试,我们发现:

  • 用户名错误:org.springframework.security.authentication.BadCredentialsException: Bad credentials
  • 密码错误:org.springframework.security.authentication.BadCredentialsException: Bad credentials
  • 用户名密码均为空:org.springframework.security.authentication.BadCredentialsException: Bad credentials
  • 用户名为空:org.springframework.security.authentication.BadCredentialsException: Bad credentials

结果发现,登陆失败的各种情况都是出现了BadCredentialsException的异常,但我们明明手动设置了如果用户找不到抛出UsernameNotFoundException异常,这显然不符合我们的预期。

我们通过跟程序,发现在 AbstractUserDetailsAuthenticationProvider 类中将hideUserNotFoundExceptions设置为true,也就是说,SpringScurity默认隐藏UsernameNotFoundException异常。

通过查阅资料发现,我们有两种方式来处理提示信息,第一种就是用户名不存在时,抛出BadCredentialsException异常,在其中存入提示信息,然而我们更喜欢用不同的异常来判断出错,现在我们修改一下,当用户名不存在时,抛出UsernameNotFoundException异常。

SpringScurity默认使用的认证处理提供者为DaoAuthenticationProvider,将UsernameNotFoundException异常给隐藏了,如果想使用UsernameNotFoundException则需要单独处理。

在这里插入图片描述

设置方式:

修改auth的认证提供者为自定义的认证提供者对象,在自定义的认证提供者对象中设置hideUserNotFoundExceptions为false,服务层认证处理程序以及密码编码器。

在这里插入图片描述

/**
     * 设置认证处理者对象
     * @return
     */
    private AuthenticationProvider authenticationProvider() {

        //创建认证处理者对象
        DaoAuthenticationProvider authenticationProvider =  new DaoAuthenticationProvider();

        //设置隐藏UserNotFoundExceptions为false(默认为true)
        authenticationProvider.setHideUserNotFoundExceptions(false);

        //设置认证的服务层处理对象
        authenticationProvider.setUserDetailsService(userDetailsService);

        //设置密码编码器对象
        authenticationProvider.setPasswordEncoder(passwordEncoder());

        return authenticationProvider;
    }

登陆失败测试:

  • 用户名错误:org.springframework.security.core.userdetails.UsernameNotFoundException: 账户名不存在
  • 密码错误:org.springframework.security.authentication.BadCredentialsException: 用户名或密码错误
  • 用户名和密码均为空:org.springframework.security.core.userdetails.UsernameNotFoundException: 账户名不存在

测试成功!

二、设置账户状态并通过异常来提示用户

之前我们在认证的服务层处理方法中,只设置了通过用户名和密码的形式来获取凭证,但我们的数据库中有一个account_states的字段,表示用户的状态,用户有三种状态“0表示正常,1表示冻结,-1表示离职”

在这里插入图片描述

现在我们通过UserDetails实现类对象User的另一种构造方法来实现利用用户状态获取凭证。

另一种构造方法:

/**
	 * Construct the <code>User</code> with the details required by
	 * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider}.
	 *
	 * @param username the username presented to the
	 * <code>DaoAuthenticationProvider</code>
	 * @param password the password that should be presented to the
	 * <code>DaoAuthenticationProvider</code>
	 * @param enabled set to <code>true</code> if the user is enabled
	 * @param accountNonExpired set to <code>true</code> if the account has not expired
	 * @param credentialsNonExpired set to <code>true</code> if the credentials have not
	 * expired
	 * @param accountNonLocked set to <code>true</code> if the account is not locked
	 * @param authorities the authorities that should be granted to the caller if they
	 * presented the correct username and password and the user is enabled. Not null.
	 *
	 * @throws IllegalArgumentException if a <code>null</code> value was passed either as
	 * a parameter or as an element in the <code>GrantedAuthority</code> collection
	 */
	public User(String username, String password, boolean enabled,
			boolean accountNonExpired, boolean credentialsNonExpired,
			boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {

		if (((username == null) || "".equals(username)) || (password == null)) {
			throw new IllegalArgumentException(
					"Cannot pass null or empty values to constructor");
		}

		this.username = username;
		this.password = password;
		this.enabled = enabled;
		this.accountNonExpired = accountNonExpired;
		this.credentialsNonExpired = credentialsNonExpired;
		this.accountNonLocked = accountNonLocked;
		this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
	}

实现方法:

 /**
     * 根据用户名获得用户信息
     *
     * @param username 用户名
     * @return 用户信息
     * @throws UsernameNotFoundException 用户名不存在异常
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account account = accountDao.queryAccountByAccountName(username);
        if (account == null){
            throw new UsernameNotFoundException("账户名不存在");
        }

        //创建一个用于存储用户认证权限的集合
        List<GrantedAuthority>  grantedAuthorities = new ArrayList<>();
        grantedAuthorities.add(new SimpleGrantedAuthority("USER"));
        /**
         * 创建UserDetails实现类对象User,将认证信息传给User对象进行认证
         * 参数1:要认证用户的用户名
         * 参数2:要认证用户的密码
         * 参数3:账户是否可以
         * 参数4:账户是否已经过期
         * 参数5:凭证是否已经过期
         * 参数6:账户是否已经被锁定
         * 参数7:要认证用户所拥有的权限集合
         */

        User userAuth = new User(
                account.getAccount_name(),
                account.getAccount_password(),
                account.getAccount_status()==0?true:account.getAccount_status()==-1?false:true,
                true,true,
                account.getAccount_status()==0?true:account.getAccount_status()==1?false:true,
                grantedAuthorities
        );

        return userAuth;
    }

测试:

  • 冻结账户:org.springframework.security.authentication.LockedException: 用户帐号已被锁定
  • 离职账户:org.springframework.security.authentication.DisabledException: 用户已失效

测试成功!

接下来实现通过异常信息来提示用户登陆失败。

首先,先编写一个Result类,来存放登录的结果。

package com.jiazhong.office.commons;

/**
 * @ClassName: Result
 * @Description: TODO 结果类
 * @Author: JiaShiXi
 * @Date: 2021/2/24 12:39
 * @Version: 1.0
 **/

public class Result {
    private boolean success;
    private String message;
    private Object data;


    private Result(boolean success, String message, Object data) {
        this.success = success;
        this.message = message;
        this.data = data;
    }

    public static Result success(){
        return new Result(true,null,null);
    }

    public static Result success(String message){
        return new Result(true,message,null);
    }

    public static Result success(String message,Object data){
        return new Result(true,message,data);
    }

    public static Result fail(){
        return new Result(false,null,null);
    }

    public static Result fail(String message){
        return new Result(false,message,null);
    }

    public static Result fail(String message,Object data){
        return new Result(false,message,data);
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

其次,修改登录控制类(LoginHandler),SpringScurity下的所有有关于认证的异常都是继承了AuthenticationException异常类,所以我们可以统一用此类来接受有关于认证的异常。

package com.jiazhong.office.controller.rbac;

import com.jiazhong.office.commons.Result;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;
import java.util.Enumeration;

/**
 * @ClassName: LoginHandler 登录控制器
 * @Description: TODO 定义一些登陆后的操作
 * @Author: JiaShiXi
 * @Date: 2021/2/23 18:37
 * @Version: 1.0
 **/

@RestController
@RequestMapping("/login")
public class LoginHandler {

    /**
     * 登陆成功后的操作
     * @return
     */
    @RequestMapping("/result/loginSuccess")
    public Result loginSuccess() {
        return Result.success("登陆成功");
    }

    /**
     * 登录失败后的操作
     * @return
     */
    @RequestMapping("/result/loginFail")
    public Result loginFail(HttpSession session){
        //获取session中错误异常
        AuthenticationException exception = (AuthenticationException) session.getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
       //自定义错误信息
        if (exception instanceof UsernameNotFoundException){
            return Result.fail("用户名不存在");
        }else if (exception instanceof BadCredentialsException){
            return Result.fail("用户名或密码错误");
        }else if (exception instanceof LockedException){
            return Result.fail("用户账号已冻结,请联系公司人事部!");
        }else if (exception instanceof DisabledException){
            return Result.fail("用户已离职,请联系公司人事部!");
        }
        return Result.fail("系统升级中,请稍后进行访问...");
    }
}

最后,修改前端显示效果。

在这里插入图片描述

this.$axios.post("/login",qs.stringify(this.account))
                  .then(response=>{
                    loading.close();//关闭加载条
                    setTimeout(()=>{
                      let result = response.data;
                      if(result.success){
                        this.$swal.fire({
                          icon: 'success',
                          title: result.message,
                          showConfirmButton: false,
                          timer: 1500
                        })
                      }else{
                        this.$swal.fire({
                          icon: 'error',
                          title: result.message,
                          showConfirmButton: false,
                          timer: 1500
                        })
                      };
                    },350);
                  })
                  .catch(err=>{
                    console.log(err)
                  })

测试,显示成功!
在这里插入图片描述

三、登陆前后视图跳转的问题

前端编写首页测试的视图:

<template>
    <div>
        首页
        <button @click="test">测试</button>
    </div>
</template>
<script>
export default {
    data(){
        return{

        }
    },
    methods:{
        test(){
            this.$axios.get("/test")
                        .then(response=>{
                            alert(response.data)
                        })
                        .catch(err=>{
                            alert(err)
                        })
        }
    }
}
</script>

提示框关闭后,进行首页的跳转:

在这里插入图片描述

我们进行测试:

登陆后:正常显示

在这里插入图片描述

未登录:显示未获取凭证错误

在这里插入图片描述

注:我们为什么可以在没有凭证的情况下访问到上面的这个页面呢?

因为只有访问后端需要认证的资源(有人也叫做"受控资源")才会进行凭证控制,上面这个页面只是前端的静态资源,不会跳转到首页,当我们点击按钮的时候,访问后端的控制器"/test",这才算受控资源。

因为在前端请求返回的都是ajax请求,所以返回的也会是ajax形式的响应,所以这时不会直接跳转到登陆页面。

此时,我们可以尝试将登陆表单设置为一个ajax返回的json数据,通过该json数据进行登录处理。

我们在LoginHandler类中添加一个用户未登陆的Controller,设置http.loginPage("/login/unLogin"),使用户未获得凭证时,访问后端资源让其跳转至"/login/unLogin"进行处理。

/**
 * 用户未登录
 * @return
 */
@RequestMapping("/unLogin")
public String unLogin(){
    return "unLogin";
}

此时要注意的是,用户未获得凭证,默认情况下,是不能访问所有后端资源的,所以我们要将"/login/unLogin"设置为不需要凭证即可访问的资源。

http.antMatchers("/login/result/loginFail","/login/unLogin").permitAll()

但是呢,此处有一个坑:

当我们在configure(HttpSecurity http)方法中加入"super.configure(http);“时,会自动调用”.anyRequest().authenticated()“将所有的后端资源变为需要认证的资源,虽然我们用http.antMatchers().permitAll()将”/login/unLogin"放行了,但是有时候就会出现一些bug,所以我们最好不要使用父类的configure方法。我们可以这样自己手动设置:

在这里插入图片描述

设置好之后,前端进行修改:

在这里插入图片描述

测试发现,当点击按钮时,就会跳转到首页了。
这种做法的原理如下:
在这里插入图片描述

这样做法的坏处是,每个前端的组件如果想要请求后端的资源的话,都要加上"unlogin"的字符串判断,前端组件非常多,这样加下去,会是一件及其麻烦的事情。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jackson Xi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值