您的位置:首页 > 编程语言 > Java开发

Spring Security4实例(Java config版)——ajax登录,自定义验证

2017-07-13 22:02 501 查看
本文源码请看这里

相关文章:

Spring Security4实例(Java config 版) —— Remember-Me

首先添加起步依赖(如果不是springboot项目,自行切换为Spring Security依赖)

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>


编写MyUserDetails类(在这里增加一个userId字段),实现UserDetails接口

public class MyUserDetails implements UserDetails{
private Integer userId;
private String username;
private String password;
private boolean enabled;
private Collection<? extends GrantedAuthority> authorities;

public MyUserDetails(Integer userId, String username, String password, boolean enabled) {
super();
this.userId = userId;
this.username = username;
this.password = password;
this.enabled = enabled;
}

public MyUserDetails(Integer userId, String username, String password, boolean enabled,
Collection<? extends GrantedAuthority> authorities) {
super();
this.userId = userId;
this.username = username;
this.password = password;
this.enabled = enabled;
this.authorities = authorities;
}
public Integer getUserId(){
return this.userId;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}

@Override
public String toString() {
return "MyUserDetails [userId=" + userId + ", username=" + username
+ ", password=" + password + ", enabled=" + enabled
+ ", authorities=" + authorities + "]";
}
}


编写UserDetailsServiceImpl类,实现UserDetailsService接口

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(this.getClass());

private final UserMapper userMapper;
private final RoleMapper roleMapper;

public UserDetailsServiceImpl(UserMapper userMapper, RoleMapper roleMapper) {
this.userMapper = userMapper;
this.roleMapper = roleMapper;
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.getUserByUsername(username);
if (user != null) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
List<String> roleTypeList = roleMapper.listRoleTypeByUsername(username);
for (String roleType : roleTypeList) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + roleType));
}
return new MyUserDetails(user.getUserId(), user.getUsername(), user.getPassword(), true, authorities);

}

throw new UsernameNotFoundException("用户名 '" + username + "'没有找到!");
}
}


编写MyUsernamePasswordAuthenticationFilter类(在这里增加验证码校验功能),继承UsernamePasswordAuthenticationFilter

public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
// 是否开启验证码功能
private boolean isOpenValidateCode = true;

public static final String VALIDATE_CODE = "validateCode";

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (isOpenValidateCode) {
checkValidateCode(request);
}
return super.attemptAuthentication(request, response);
}

protected void checkValidateCode(HttpServletRequest request) {
HttpSession session = request.getSession();

String sessionValidateCode = obtainSessionValidateCode(session);
sessionValidateCode = "1234";// 做个假的验证码;
// 让上一次的验证码失效
session.setAttribute(VALIDATE_CODE, null);
String validateCodeParameter = obtainValidateCodeParameter(request);
if (StringUtils.isEmpty(validateCodeParameter) || !sessionValidateCode.equalsIgnoreCase(validateCodeParameter)) {
throw new AuthenticationServiceException("验证码错误!");
}
}

private String obtainValidateCodeParameter(HttpServletRequest request) {
Object obj = request.getParameter(VALIDATE_CODE);
return null == obj ? "" : obj.toString();
}

protected String obtainSessionValidateCode(HttpSession session) {
Object obj = session.getAttribute(VALIDATE_CODE);
return null == obj ? "" : obj.toString();
}

}


编写配置类WebSecurityConfig继承WebSecurityConfigurerAdapter(下面注入的2个mapper就是mybatis的接口)

@Configuration
@EnableWebSecurity
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
UserMapper userMapper;

@Autowired
RoleMapper roleMapper;

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

/**
* 忽略静态文件
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/frame/**", "/img/**", "/css/**");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/login/**").permitAll()
// user权限可以访问的请求
.antMatchers("/security/user").hasRole("user")
// admin权限可以访问的请求
.antMatchers("/security/admin").hasRole("admin")
// SpEL表达式:需要拥有user权限,且进行了完全认证
.antMatchers("/user/account").access("hasRole('user') and isFullyAuthenticated()")
// 其他地址的访问均需验证权限(需要登录)
.anyRequest().authenticated().and()
// 添加验证码验证
.addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login_page")).and()
// 指定登录页面的请求路径
.formLogin().loginPage("/login_page")
// 登陆处理路径
.loginProcessingUrl("/login").permitAll().and()
// 退出请求的默认路径为logout,下面改为signout,
// 成功退出登录后的url可以用logoutSuccessUrl设置
.logout().logoutUrl("/signout").logoutSuccessUrl("/login_page").permitAll().and()
// 关闭csrf
.csrf().disable();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceImpl()).passwordEncoder(new Md5PasswordEncoder())

}

@Bean
public UserDetailsServiceImpl userDetailsServiceImpl() {
return new UserDetailsServiceImpl(userMapper, roleMapper);
}

@Bean
public MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {
MyUsernamePasswordAuthenticationFilter myFilter = new MyUsernamePasswordAuthenticationFilter();
myFilter.setAuthenticationManager(authenticationManagerBean());
myFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
myFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
myFilter.setRememberMeServices(tokenBasedRememberMeServices());
return myFilter;
}

@Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
return new SimpleUrlAuthenticationSuccessHandler("/login/success");
}

@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new SimpleUrlAuthenticationFailureHandler("/login/failure");
}
}


编写UserController进行一些业务处理和测试

@RestController
public class UserController {
private RequestCache requestCache = new HttpSessionRequestCache();

@RequestMapping(value = "/login_page", method = RequestMethod.GET)
public ModelAndView loginPage(HttpServletRequest request) {
if (HttpHelper.isAjaxRequest(request)) {
return new ModelAndView("/login/ajax");
} else {
return new ModelAndView("login.html");
}

}

@RequestMapping(value = "/login/success", method = RequestMethod.GET)
public Map<String, Object> loginSuccess(HttpServletRequest request, HttpServletResponse response) {
SavedRequest savedRequest = requestCache.getRequest(request, response);
String targetUrl = null;
if (savedRequest != null) {
targetUrl = savedRequest.getRedirectUrl();
}
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", true);
result.put("targetUrl", targetUrl);
return result;
}

@RequestMapping(value = "/login/failure", method = RequestMethod.GET)
public Map<String, Object> loginFailure(HttpServletRequest request, HttpServletResponse response) {
AuthenticationException ae = (AuthenticationException) request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", false);
result.put("message", ae.getMessage());
return result;
}

@RequestMapping(value = "/login/ajax", method = RequestMethod.GET)
public Map<String, Object> loginAjax() {
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", false);
result.put("message", "you need login!");
return result;
}

@RequestMapping(value = "/security/user", method = RequestMethod.GET)
public Map<String, Object> securityUser(HttpServletRequest request) {
MyUserDetails user = UserUtil.getCurrentUser();
Map<String, Object> result = new HashMap<String, Object>();
StringBuilder userRole = new StringBuilder();
if (user != null) {
result.put("userId", user.getUserId());
result.put("userName", user.getUsername());
Collection<? extends GrantedAuthority> roleLst = user.getAuthorities();
for (GrantedAuthority sga : roleLst) {
userRole.append(sga.toString() + "; ");
}
}
result.put("userRole", userRole.toString());
result.put("message", "This message is only visible to the user");
return result;
}

@RequestMapping(value = "/security/admin", method = RequestMethod.GET)
public Map<String, Object> securityAdmin(HttpServletRequest request) {
MyUserDetails user = UserUtil.getCurrentUser();
Map<String, Object> result = new HashMap<String, Object>();
StringBuilder userRole = new StringBuilder();
if (user != null) {
result.put("userId", user.getUserId());
result.put("userName", user.getUsername());
Collection<? extends GrantedAuthority> roleLst = user.getAuthorities();
for (GrantedAuthority sga : roleLst) {
userRole.append(sga.toString() + "; ");
}
}
result.put("userRole", userRole.toString());
result.put("message", "This message is only visible to the admin");
return result;
}

@RequestMapping(value = "/user/account", method = RequestMethod.GET)
public Map<String, Object> getUserAcctunt(HttpServletRequest request) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("message", "需要进行完整认证的请求(不是通过Remember-me功能进行的认证)");
return result;
}
}

登录页 login.html 和 首页 index.html

还有个获取当前用户的工具类:

/**
* 用户工具类
*/
public class UserUtil {
/*
* 获取当前用户
*
* @return
*/
public static MyUserDetails getCurrentUser() {
MyUserDetails user = null;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = null;
if (authentication != null) {
principal = authentication.getPrincipal();
}
if (principal != null && principal instanceof MyUserDetails) {
user = (MyUserDetails) principal;
}
return user;
}
}


部分说明:

当没有登录的时候,访问一个受限制的页面或者一个ajax请求时,需要被重定向到登录页(请求方法.loginPage("/login_page")里定义的路径),因为如果"/login_page"直接返回一个页面的话,ajax是无法处理的,所以要先判断是否为ajax请求,如果是则返回一个错误标识,然后在页面进行后续处理,如果不是则直接返回登录页。

@RequestMapping(value = "/login_page", method = RequestMethod.GET)
public ModelAndView loginPage(HttpServletRequest request) {
if (HttpHelper.isAjaxRequest(request)) {
return new ModelAndView("/login/ajax");
} else {
return new ModelAndView("login.html");
}

}

@RequestMapping(value = "/login/ajax", method = RequestMethod.GET)
public Map<String, Object> loginAjax() {
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", false);
result.put("message", "you need login!");
return result;
}

效果如图



当定义了自己的MyUsernamePasswordAuthenticationFilter时候,需要在定义bean的时候设置认证成功和认证失败的处理,如果没有设置认证失败的处理则会返回一个403的错误,下面分别定义了2个处理路径

@Bean
public MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {
MyUsernamePasswordAuthenticationFilter myFilter = new MyUsernamePasswordAuthenticationFilter();
myFilter.setAuthenticationManager(authenticationManagerBean());
myFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
myFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
myFilter.setRememberMeServices(tokenBasedRememberMeServices());
return myFilter;
}

@Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
return new SimpleUrlAuthenticationSuccessHandler("/login/success");
}

@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new SimpleUrlAuthenticationFailureHandler("/login/failure");
}

/**
* 如果是访问受限页面后,跳转到登录页的,则在targetUrl保存之前受限页面的路径,供页面调用
*
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/login/success", method = RequestMethod.GET)
public Map<String, Object> loginSuccess(HttpServletRequest request, HttpServletResponse response) {
SavedRequest savedRequest = requestCache.getRequest(request, response);
String targetUrl = null;
if (savedRequest != null) {
targetUrl = savedRequest.getRedirectUrl();
}
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", true);
result.put("targetUrl", targetUrl);
return result;
}

/**
* 获取异常信息返回给页面
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/login/failure", method = RequestMethod.GET)
public Map<String, Object> loginFailure(HttpServletRequest request, HttpServletResponse response) {
AuthenticationException ae = (AuthenticationException) request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", false);
result.put("message", ae.getMessage());
return result;
}


注意:

@Overide
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}

@Autowired
public void whatever(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}

以上两个方法只是写法不同,效果是一样的,主要有如下限制:

configure方法是重写所以需要继承 WebSecurityConfigurerAdapter类;

whatever方法意思就是方法名是什么并不重要(例如可以叫configureGlobal ),但是它需要:

方法上标记@Autowired注解

类上有@EnableWebSecurity, @EnableWebMvcSecurity, @EnableGlobalMethodSecurity, 或@EnableGlobalAuthentication其中之一的注解

包含AuthenticationManagerBuilder参数;

stackoverflow上有详细介绍,请看这里

Spring Security 4.1.0版本的文档上也有提及:


The name of the configureGlobal method is not important. However, it is important to only configure AuthenticationManagerBuilder in a class annotated with either @EnableWebSecurity, @EnableGlobalMethodSecurity, or @EnableGlobalAuthentication. Doing otherwise has unpredictable results.



参考资料:

Spring实战(第4版)

http://lengyun3566.iteye.com/blog/pdf

http://blog.csdn.net/u010425898/article/details/53690140?locationNum=4&fps=1

http://blog.csdn.net/jaune161/article/details/18502265

http://www.iteye.com/topic/720867

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: