Spring Security总结之如何让认证失败消息自定义在前端页面显示(一)

采用springboot+thymeleaf

前端登录页面代码

<form action="/doLogin" method="post">
	用户名:<input type="text" name="username" id="username"/><br/>
	密码:<input type="password" name="password" id="password"/><br/>
	<button id="submit">登录</button>
</form>
<!-- 以下为显示认证失败等提示信息(th:if=""一定要写 ) -->     
<div th:if="${param.error}" th:text="${session?.SPRING_SECURITY_LAST_EXCEPTION?.message}"></div>

spring security配置文件

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

	@Autowired
	SessionRegistry sessionRegistry;
	
	
	@Bean
	public SessionRegistry sessionRegistry() {
		return new SessionRegistryImpl();
	}
	
	/** session失效处理 */
	@Bean
	public SessionInformationExpiredStrategy expiredSessionStrategy() {
		return new ExpiredSessionStrategy();
	}
	
	/** 获取数据库中信息存到User对象中 */
	@Bean
    public UserDetailsService userService(){ 
        //实现了UserDetailsService接口
        return new UserServiceImpl();
    }
	
	/**security自带加密*/
	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
	
	/**自定义MD5加密*/
	@Bean
	PasswordEncoder md5PasswordEncoder() {
		return new MD5PasswordEncoder();
	}
	
	/** 放行静态资源 */
	@Override
    public void configure(WebSecurity web) throws Exception {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/css/**");
        web.ignoring().antMatchers("/js/**");
        web.ignoring().antMatchers("/images/**");
        web.ignoring().antMatchers("/login/**");
        //解决服务注册url被拦截的问题
        web.ignoring().antMatchers("/resources/**");
        
    }
	/** 授权 */
	@Override
    protected void configure(HttpSecurity http) throws Exception {
    	
		//解决非thymeleaf的form表单提交被拦截问题
		http.csrf().disable();
        // 登录
		http.formLogin().loginPage("/login")
		    .loginProcessingUrl("/doLogin")
		    .successForwardUrl("/index")
		    .failureUrl("/login?error=true");  
		//授权
		http
			.authorizeRequests()
                .anyRequest()
                .authenticated()
				.and()
				.formLogin()
                .loginPage("/login").permitAll()
                ;
                
        //session管理
        //session失效后跳转  
        http.sessionManagement().invalidSessionUrl("/login"); 

        //单用户登录,如果有一个登录了,同一个用户在其他地方登录将前一个剔除下线
        //http.sessionManagement().maximumSessions(1).expiredSessionStrategy(expiredSessionStrategy());
        
		//单用户登录,如果有一个登录了,同一个用户在其他地方不能登录
	    http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);
	    //退出删除cookie
	    http.logout().deleteCookies("JESSIONID");
	    
	    //解决中文乱码问题
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        filter.setForceEncoding(true);
        http.addFilterBefore(filter,CsrfFilter.class)
		
	}
	
	/**
	 * 认证器
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		
		//将用户信息写入内存
		//auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("huihui").password(new BCryptPasswordEncoder().encode("123456")).roles("USER");
		
		// 使用数据库中用户数据,使用自定义的加密方法
		auth.userDetailsService(userService()).passwordEncoder(md5PasswordEncoder());
		
	}
	
}

用户实体类

需要实现security的UserDetails类

public class T_user implements UserDetails {
	private String username;
	private String password;
    //  ... 省略get,set 方法
    @Override
	public String toString() {
		return this.username;
	}
	@Override
	public int hashCode() {
		return username.hashCode();
	}
	
	@Override
	public boolean equals(Object obj) {
		return this.toString().equals(obj.toString());
	}

	// 权限
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		// TODO Auto-generated method stub
		return null;
	}

	// 账号是否过期
	@Override
	public boolean isAccountNonExpired() {
		// TODO Auto-generated method stub
		return true;
	}

	// 账号是否锁定
	@Override
	public boolean isAccountNonLocked() {
		// TODO Auto-generated method stub
		return true;
	}

	// 密码是否过期
	@Override
	public boolean isCredentialsNonExpired() {
		// TODO Auto-generated method stub
		return true;
	}

	// 账号是否可用
	@Override
	public boolean isEnabled() {
		// TODO Auto-generated method stub
		return true;
	}
}

登录认证实现类

需要实现security的UserDetailsService接口,重写它的loadUserByUsername方法

@Service
public class UserServiceImpl implements UserDetailsService  {

	@Autowired
	private UserMapper userMapper;
	@Autowired
    private SessionRegistry sessionRegistry;
	//@Autowired
	//private PasswordEncoder passwordEncoder;
	@Autowired
	private MD5PasswordEncoder md5PasswordEncoder;

	/**
	 * 重写springSecurity类方法
	 */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// 通过用户名称获取对象
		T_user user = userMapper.findByUserName(username);
		
		if (user != null) {			
			// 通过用户id查询角色
			List<T_role> roles = user.getRoles();
			List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
			for (T_role role : roles) {
				if (role != null && role.getName() != null) {

					GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getName());
					// 1:此处将角色信息添加到 GrantedAuthority
					// 对象中,在后面进行全权限验证时会使用GrantedAuthority 对象。
					grantedAuthorities.add(grantedAuthority);
				}
			}
			//因为我测试,数据库中的密码是没有加密的,所以在这里加密了
			String password = md5PasswordEncoder.encode(user.getPassword());
			//这个User对象是security的,不是我们自定义的
			return new User(user.getUsername(), password, grantedAuthorities);

		} else {
			throw new UsernameNotFoundException("admin: " + username + " 不存在!");
		}
				
	}	
	
}

登录错误时返回自定义的消息

写一个消息配置文件

@Configuration
public class MySecurityMessages {
	/** 注册bean */
	@Bean
	public MessageSource messageSource() {
        Locale.setDefault(Locale.CHINA);
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        /**
		 *	下面为加载自定义消息的properties文件,我放在了maven结构resources下,里面的提示消息可以自己定义,
		 *	比如:密码错误是,原文件中提示的是,坏的凭证,我们可以找到它对应的key,修改它的值为 用户名或密码错误。
		 */
        messageSource.addBasenames("classpath:messages_zh_CN");
 
        return messageSource;
    }
	
}

下面是messages_zh_CN.properties中的内容

AbstractAccessDecisionManager.accessDenied=\u4E0D\u5141\u8BB8\u8BBF\u95EE
AbstractLdapAuthenticationProvider.emptyPassword=\u574F\u7684\u51ED\u8BC1
AbstractSecurityInterceptor.authenticationNotFound=\u672A\u5728SecurityContext\u4E2D\u67E5\u627E\u5230\u8BA4\u8BC1\u5BF9\u8C61
AbstractUserDetailsAuthenticationProvider.badCredentials=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF
AbstractUserDetailsAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
AbstractUserDetailsAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548
AbstractUserDetailsAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
AbstractUserDetailsAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
AbstractUserDetailsAuthenticationProvider.onlySupports=\u4EC5\u4EC5\u652F\u6301UsernamePasswordAuthenticationToken
AccountStatusUserDetailsChecker.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
AccountStatusUserDetailsChecker.disabled=\u7528\u6237\u5DF2\u5931\u6548
AccountStatusUserDetailsChecker.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
AccountStatusUserDetailsChecker.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
AclEntryAfterInvocationProvider.noPermission=\u7ED9\u5B9A\u7684Authentication\u5BF9\u8C61({0})\u6839\u672C\u65E0\u6743\u64CD\u63A7\u9886\u57DF\u5BF9\u8C61({1})
AnonymousAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684AnonymousAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
BindAuthenticator.badCredentials=\u574F\u7684\u51ED\u8BC1
BindAuthenticator.emptyPassword=\u574F\u7684\u51ED\u8BC1
CasAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684CasAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
CasAuthenticationProvider.noServiceTicket=\u672A\u80FD\u591F\u6B63\u786E\u63D0\u4F9B\u5F85\u9A8C\u8BC1\u7684CAS\u670D\u52A1\u7968\u6839
ConcurrentSessionControlAuthenticationStrategy.exceededAllowed=\u8BE5\u8D26\u53F7\u6B63\u5728\u5176\u4ED6\u5730\u65B9\u767B\u5F55
DigestAuthenticationFilter.incorrectRealm=\u54CD\u5E94\u7ED3\u679C\u4E2D\u7684Realm\u540D\u5B57({0})\u540C\u7CFB\u7EDF\u6307\u5B9A\u7684Realm\u540D\u5B57({1})\u4E0D\u543B\u5408
DigestAuthenticationFilter.incorrectResponse=\u9519\u8BEF\u7684\u54CD\u5E94\u7ED3\u679C
DigestAuthenticationFilter.missingAuth=\u9057\u6F0F\u4E86\u9488\u5BF9'auth' QOP\u7684\u3001\u5FC5\u987B\u7ED9\u5B9A\u7684\u6458\u8981\u53D6\u503C; \u63A5\u6536\u5230\u7684\u5934\u4FE1\u606F\u4E3A{0}
DigestAuthenticationFilter.missingMandatory=\u9057\u6F0F\u4E86\u5FC5\u987B\u7ED9\u5B9A\u7684\u6458\u8981\u53D6\u503C; \u63A5\u6536\u5230\u7684\u5934\u4FE1\u606F\u4E3A{0}
DigestAuthenticationFilter.nonceCompromised=Nonce\u4EE4\u724C\u5DF2\u7ECF\u5B58\u5728\u95EE\u9898\u4E86\uFF0C{0}
DigestAuthenticationFilter.nonceEncoding=Nonce\u672A\u7ECF\u8FC7Base64\u7F16\u7801; \u76F8\u5E94\u7684nonce\u53D6\u503C\u4E3A {0}
DigestAuthenticationFilter.nonceExpired=Nonce\u5DF2\u7ECF\u8FC7\u671F/\u8D85\u65F6
DigestAuthenticationFilter.nonceNotNumeric=Nonce\u4EE4\u724C\u7684\u7B2C1\u90E8\u5206\u5E94\u8BE5\u662F\u6570\u5B57\uFF0C\u4F46\u7ED3\u679C\u5374\u662F{0}
DigestAuthenticationFilter.nonceNotTwoTokens=Nonce\u5E94\u8BE5\u7531\u4E24\u90E8\u5206\u53D6\u503C\u6784\u6210\uFF0C\u4F46\u7ED3\u679C\u5374\u662F{0}
DigestAuthenticationFilter.usernameNotFound=\u7528\u6237\u540D{0}\u672A\u627E\u5230
JdbcDaoImpl.noAuthority=\u6CA1\u6709\u4E3A\u7528\u6237{0}\u6307\u5B9A\u89D2\u8272
JdbcDaoImpl.notFound=\u672A\u627E\u5230\u7528\u6237{0}
LdapAuthenticationProvider.badCredentials=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF
LdapAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
LdapAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548
LdapAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
LdapAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
LdapAuthenticationProvider.emptyUsername=\u7528\u6237\u540D\u4E0D\u5141\u8BB8\u4E3A\u7A7A
LdapAuthenticationProvider.onlySupports=\u4EC5\u4EC5\u652F\u6301UsernamePasswordAuthenticationToken
PasswordComparisonAuthenticator.badCredentials=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF
#PersistentTokenBasedRememberMeServices.cookieStolen=Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.
ProviderManager.providerNotFound=\u672A\u67E5\u627E\u5230\u9488\u5BF9{0}\u7684AuthenticationProvider
RememberMeAuthenticationProvider.incorrectKey=\u5C55\u793ARememberMeAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
RunAsImplAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684RunAsUserToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
SubjectDnX509PrincipalExtractor.noMatching=\u672A\u5728subjectDN\: {0}\u4E2D\u627E\u5230\u5339\u914D\u7684\u6A21\u5F0F
SwitchUserFilter.noCurrentUser=\u4E0D\u5B58\u5728\u5F53\u524D\u7528\u6237
SwitchUserFilter.noOriginalAuthentication=\u4E0D\u80FD\u591F\u67E5\u627E\u5230\u539F\u5148\u7684\u5DF2\u8BA4\u8BC1\u5BF9\u8C61

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值