<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>
@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