Spring Security4实例(Java config版)——ajax登录,自定义验证
2017-07-13 22:02
501 查看
本文源码请看这里
相关文章:
Spring Security4实例(Java config 版) —— Remember-Me
首先添加起步依赖(如果不是springboot项目,自行切换为Spring Security依赖)
编写MyUserDetails类(在这里增加一个userId字段),实现UserDetails接口
编写UserDetailsServiceImpl类,实现UserDetailsService接口
编写MyUsernamePasswordAuthenticationFilter类(在这里增加验证码校验功能),继承UsernamePasswordAuthenticationFilter
编写配置类WebSecurityConfig继承WebSecurityConfigurerAdapter(下面注入的2个mapper就是mybatis的接口)
编写UserController进行一些业务处理和测试
登录页 login.html 和 首页 index.html
还有个获取当前用户的工具类:
部分说明:
当没有登录的时候,访问一个受限制的页面或者一个ajax请求时,需要被重定向到登录页(请求方法.loginPage("/login_page")里定义的路径),因为如果"/login_page"直接返回一个页面的话,ajax是无法处理的,所以要先判断是否为ajax请求,如果是则返回一个错误标识,然后在页面进行后续处理,如果不是则直接返回登录页。
效果如图
当定义了自己的MyUsernamePasswordAuthenticationFilter时候,需要在定义bean的时候设置认证成功和认证失败的处理,如果没有设置认证失败的处理则会返回一个403的错误,下面分别定义了2个处理路径
注意:
以上两个方法只是写法不同,效果是一样的,主要有如下限制:
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
相关文章:
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
相关文章推荐
- 使用java编写SmartFoxServer自定义安全验证登录扩展
- [Java]利用拦截器和自定义注解做登录以及权限验证
- prototye+java实现ajax登录实例
- Java乔晓松-ajax实现用户名和邮箱唯一验证实例(struts2框架和HIbernate)
- 用户登录验证Ajax实例详解
- 用户自定义的Alert弹出框和ajax验证登录
- 简单的Spring Security实例(自定义登录验证)
- Java 关于Ajax的实例--验证用户名(四)
- Ajax小实例验证登录框---经验总结
- 自定义Django中间件(登录验证中间件实例)
- 一个很好的ajax入门小实例,用户登录验证
- Java 关于Ajax的实例--验证用户名(四)
- 基于JSP + servlet + javabean的MVC简单验证登录实例
- java-web-Filter-登录验证之筛选器使用实例
- 使用java编写SmartFoxServer自定义安全验证登录扩展
- 【java】struts和ajax的小例子①→后台的登录验证
- Ajax java struts 实现用户名验证
- Asp.Net Forms验证(自定义、角色提供程序、单点登录)
- Struts实例详解1--登录验证
- 利用Forms验证(自定义、角色提供程序、单点登录)登陆 (转)