spring-security 实现用户名密码/图片验证码验证和记住我以及登录次数判断功能
基于RBAC权限模式下的spring-security用法
- 加载权限资源(MyInvocationSecurityMetadataSourceService)
- 添加访问决策(MyAccessDecisionManager)
- 添加权限拦截器(MyFilterSecurityInterceptor)
- 加载用户权限(UserDetailService)
执行流程
- 用户提交登录信息
- 进入内置的拦截器
UsernamePasswordAuthenticationFilter
处理用户名密码正确性 - 验证完成后去加载用户权限(UserDetailService)
- 进入到权限拦截器(MyFilterSecurityInterceptor)
- 在权限拦截器里去调用MyInvocationSecurityMetadataSourceService的getAttributes()方法获取对应的所有权限
- 再调用MyAccessDecisionManager的decide方法来校验用户是否有对应的权限
上面流程是我断点看到的顺序,如有错误,欢迎在下面评论指出
下面给出相应的类源码,基本上都是固定写法
基本配置
加载权限资源(MyInvocationSecurityMetadataSourceService)
@Service
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
@Autowired
private PermissionService permissionService;
private Map<String, Collection<ConfigAttribute>> map = null;
private void loadResource() {
map = new HashMap<>();
Collection<ConfigAttribute> array;
ConfigAttribute cfg;
List<Permission> permissions = permissionService.findAll();
for (Permission permission : permissions) {
array = new ArrayList<>();
cfg = new SecurityConfig(permission.getValue());
array.add(cfg);
map.put(permission.getUrl(), array);
}
}
/**
* 加载权限资源
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
if (map == null) {
this.loadResource();
}
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
AntPathRequestMatcher matcher;
String resUrl;
for (Iterator<String> iter = map.keySet().iterator(); iter.hasNext();) {
resUrl = iter.next();
matcher = new AntPathRequestMatcher(resUrl);
if (matcher.matches(request)) {
return map.get(resUrl);
}
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return Collections.emptyList();
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
访问决策(MyAccessDecisionManager)
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
/**
* 判定是否拥有权限的决策方法,
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
if (null == configAttributes || configAttributes.size() <= 0) {
return;
}
ConfigAttribute c;
String needRole;
for (Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext();) {
c = iter.next();
needRole = c.getAttribute();
for (GrantedAuthority ga : authentication.getAuthorities()) {
if (needRole.trim().equals(ga.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("no right");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
权限拦截器(MyFilterSecurityInterceptor)
@Service
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}
加载用户权限(UserDetailService)
@Service
public class MyUserDetailService implements UserDetailsService {
@Autowired
private AdminUserService adminUserService;
@Autowired
private PermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AdminUser adminUser = adminUserService.findByUsername(username);
List<Permission> permissions = permissionService.findByAdminUserId(adminUser.getId());
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for (Permission permission : permissions) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName());
grantedAuthorities.add(grantedAuthority);
}
return new User(adminUser.getUsername(), adminUser.getPassword(), grantedAuthorities);
}
}
SecurityConfig配置如下
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailService myUserDetailService;
@Autowired
private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**")
.authenticated();
http.formLogin()
.loginPage("/adminlogin")
.loginProcessingUrl("/adminlogin")
.failureUrl("/adminlogin?error")
.defaultSuccessUrl("/admin/dashboard")
.permitAll();
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/admin/logout"))
.logoutSuccessUrl("/adminlogin")
.deleteCookies("JSESSIONID", "remember-me");
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider());
}
@Bean
public AuthenticationProvider authenticationProvider() {
MyAuthenticationProvider provider = new MyAuthenticationProvider();
provider.setPasswordEncoder(new BCryptPasswordEncoder());
provider.setUserDetailsService(myUserDetailService);
return provider;
}
}
添加验证码校验
校验验证码通过实现 GenericFilterBean
过滤器来处理,原码如下
@Component
public class MyGenericFilterBean extends GenericFilterBean {
private String defaultFilterProcessUrl = "/adminlogin";
@Autowired
private SiteConfig siteConfig;
@Autowired
private AdminUserService adminUserService;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 判断是post请求,并且请求地址是 /adminlogin
if ("POST".equalsIgnoreCase(request.getMethod()) && defaultFilterProcessUrl.equals(request.getServletPath())) {
// 验证码验证
String requestCaptcha = request.getParameter("code");
String genCaptcha = (String) request.getSession().getAttribute("index_code");
if (StringUtils.isEmpty(requestCaptcha))
throw new AuthenticationServiceException("验证码不能为空!");
if (!genCaptcha.toLowerCase().equals(requestCaptcha.toLowerCase())) {
throw new AuthenticationServiceException("验证码错误!");
}
// 判断登陆次数及上限时间
String username = obtainUsername(request);
AdminUser adminUser = adminUserService.findByUsername(username);
if (adminUser == null) {
throw new AuthenticationServiceException("用户名或密码错误!");
} else {
if (adminUser.getAttempts() >= siteConfig.getAttempts()) {
Calendar dateOne = Calendar.getInstance(), dateTwo = Calendar.getInstance();
dateOne.setTime(new Date());
dateTwo.setTime(adminUser.getAttemptsTime());
long timeOne = dateOne.getTimeInMillis();
long timeTwo = dateTwo.getTimeInMillis();
long minute = (timeOne - timeTwo) / (1000 * 60);// 转化minute
if (minute < siteConfig.getAttemptsWaitTime()) {
throw new AuthenticationServiceException(
"密码错误超过" + siteConfig.getAttempts() + "次,账号已被锁定" + siteConfig.getAttemptsWaitTime() + "分钟");
} else {
adminUser.setAttempts(0);
}
}
}
}
chain.doFilter(request, response);
}
}
SecurityConfig配置添加一个filter
@Autowired
private MyGenericFilterBean myGenericFilterBean;
@Override
protected void configure(HttpSecurity http) throws Exception {
// ...
http.addFilterBefore(myGenericFilterBean, UsernamePasswordAuthenticationFilter.class);
// ...
}
登录成功记住我
先开发一个实体类记录rememberMeToken
@Entity
@Table
public class RememberMeToken implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String series;
private String tokenValue;
private Date date;
// getter , setter
}
添加对应的repository
public interface RememberMeTokenRepository extends JpaRepository<RememberMeToken, Integer> {
RememberMeToken getBySeries(String series);
void deleteByUsername(String username);
List<RememberMeToken> getAllByUsernameOrderByDate(String username);
}
添加对应的service
@Service
@Transactional
public class PersistentTokenService implements PersistentTokenRepository {
@Autowired
private RememberMeTokenRepository rememberMeTokenRepository;
@Autowired
private SiteConfig siteConfig;
@Override
public void createNewToken(PersistentRememberMeToken token) {
List<RememberMeToken> tokens = rememberMeTokenRepository.getAllByUsernameOrderByDate(token.getUsername());
if (tokens != null && tokens.size() >= siteConfig.getLoginPoints()) {
int end = tokens.size() - siteConfig.getLoginPoints() + 1;
for (int i = 0; i < end; i++) {
rememberMeTokenRepository.delete(tokens.get(i));
}
}
RememberMeToken rememberMeToken = new RememberMeToken();
BeanUtils.copyProperties(token, rememberMeToken);
rememberMeTokenRepository.save(rememberMeToken);
}
@Override
public void updateToken(String series, String tokenValue, Date lastUsed) {
RememberMeToken rememberMeToken = rememberMeTokenRepository.getBySeries(series);
if (rememberMeToken != null) {
rememberMeToken.setTokenValue(tokenValue);
rememberMeToken.setDate(lastUsed);
}
}
@Override
public PersistentRememberMeToken getTokenForSeries(String seriesId) {
RememberMeToken rememberMeToken = rememberMeTokenRepository.getBySeries(seriesId);
if (rememberMeToken != null) {
return new PersistentRememberMeToken(rememberMeToken.getUsername(),
rememberMeToken.getSeries(), rememberMeToken.getTokenValue(), rememberMeToken.getDate());
}
return null;
}
@Override
public void removeUserTokens(String username) {
rememberMeTokenRepository.deleteByUsername(username);
}
}
在 SecurityConfig 里配置一个记住我的服务Bean
@Autowired
private PersistentTokenService persistentTokenService;
@Override
protected void configure(HttpSecurity http) throws Exception {
// ...
http.authorizeRequests().and().rememberMe().rememberMeServices(persistentTokenBasedRememberMeServices());
// ...
}
@Bean
public PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices() {
PersistentTokenBasedRememberMeServices services = new PersistentTokenBasedRememberMeServices("remember-me"
, myUserDetailService, persistentTokenService);
services.setAlwaysRemember(true);
return services;
}
至此,标题中列出的功能都实现了,具体代码见项目:https://github.com/tomoya92/spring-boot-security-demo
原文链接:https://tomoya92.github.io/2018/05/21/spring-security-capatch-remember-me/
参考
原文链接: https://tomoya92.github.io/2018/05/21/spring-security-capatch-remember-me/
更多内容请访问:IT源点
注意:本文归作者所有,未经作者允许,不得转载