<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-securityartifactId> dependency>
2.增加一个配置类MySecurityConfig,该类继承WebSecurityConfigurerAdapter;
package com.lxht.emos.config; import com.lxht.emos.bean.MenuBean; import com.lxht.emos.security.MyUserDetailsService; import com.lxht.emos.security.MyValidCodeProcessingFilter; import com.lxht.emos.service.MenuService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; import java.util.List; @EnableWebSecurity public class MySecurityConfig extends WebSecurityConfigurerAdapter { private String loginPage = "/userLogin"; private String failureUrl = "/userLogin?error=T"; private String successUrl = "/userLogin?error=F"; private String applyCheckCode = "/applyCheckCode"; @Autowired MenuService menuService; public MySecurityConfig() { super(true); } @Override public void configure(HttpSecurity http) throws Exception { http.securityContext().securityContextRepository(securityContextRepository()); //从数据库读取对应的url地址及权限角色 ListmenuBeanList = menuService.getMenus(); for(MenuBean tmpBean : menuBeanList) {
http.authorizeRequests().antMatchers(tmpBean.getMenuUrl()).hasAnyRole(tmpBean.getRoleIds().split(","));
}
//设置登录界面
http.authorizeRequests()
.and()
//增加一个对验证码进行判断的filte .addFilterBefore(validCodeProcessingFilter(),UsernamePasswordAuthenticationFilter.class) .formLogin().loginPage(getLoginPage()).failureUrl(getFailureUrl())
.failureForwardUrl(getFailureUrl()). successForwardUrl(getSuccessUrl()).permitAll(); }
@Override public void configure(AuthenticationManagerBuilder auth) throws Exception {
//设置根据用户名获取用户信息的自定义userDetailsService类,该类从数据库中读用用户信息auth.userDetailsService(userDetailsService());
} @Override public void configure(WebSecurity web) throws Exception { super.configure(web); web.ignoring().antMatchers(applyCheckCode); //设置申请验证码的url不进行访问控制 } @SuppressWarnings("deprecation") @Bean public static NoOpPasswordEncoder passwordEncoder() {
//这里使用了密码不进行加密验证,正式项目还是必须要用加密验证方式 return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); }@Bean
//设置对spring security的UserDetails进行session保存,这个必须要有,不然不会保存至session对应的缓存redis中 HttpSessionSecurityContextRepository httpSessionSecurityContextRepository = new HttpSessionSecurityContextRepository(); return httpSessionSecurityContextRepository; } @Bean public MyValidCodeProcessingFilter validCodeProcessingFilter() throws Exception{
//设置对应的验证码验证filter,上面configure方法中通过 //.addFilterBefore(validCodeProcessingFilter(),UsernamePasswordAuthenticationFilter.class) //将该filter放至UsernamePasswordAuthenticationFilter之间 MyValidCodeProcessingFilter myValidCodeProcessingFilter = new MyValidCodeProcessingFilter(getLoginPage());
myValidCodeProcessingFilter.getFailureHandler().setDefaultFailureUrl(getFailureUrl()); myValidCodeProcessingFilter.setAuthenticationManager(this.authenticationManager()); return myValidCodeProcessingFilter; } public String getLoginPage() { return loginPage; } public void setLoginPage(String loginPage) { this.loginPage = loginPage; } public String getFailureUrl() { return failureUrl; } public void setFailureUrl(String failureUrl) { this.failureUrl = failureUrl; } public String getSuccessUrl() { return successUrl; } public void setSuccessUrl(String successUrl) { this.successUrl = successUrl; } public String getApplyCheckCode() { return applyCheckCode; } public void setApplyCheckCode(String applyCheckCode) { this.applyCheckCode = applyCheckCode; } } 下面对该类代码进行一下详细的解译:
(1)@EnableWebSecurity表示启用spring security进行web访问控制;
(2)引入MenuService menuService,表示从数据库读取配置的url访问控制;menuService从数据库(mysql)中读取的数据格式(表名:sys_menus)如下:
menu_id | menu_name | menu_url | rold_ids |
1 | 用户登录 | /userAuth | 1,2 |
2 | 总控开关 | /** | 1,2 |
其中role_ids表示访问该url项的角色id,多个角色以,分隔; (3)构造方法,super(true)表示取消spring security的默认设置项,因为我的应用结合了cache redis和session redis, 发现不取消默认设置项,自定义的userDetailsService不执行;
public MySecurityConfig() { super(true); } (4)configure(HttpSecurity http)方法代码说明 略,详见方法听注释 (5)对应的MyUserDetailsService类的内容:
package com.lxht.emos.security; import com.lxht.emos.bean.UserBean; import com.lxht.emos.bean.UserRolesBean; import com.lxht.emos.bean.UserSessionBean; import com.lxht.emos.service.UserService; import com.lxht.emos.utils.ConstantVal; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.util.AutoPopulatingList; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class MyUserDetailsService implements UserDetailsService { @Autowired private UserService userService; //从数据库中读取用户的信息 @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { ArrayListauthorities = new ArrayList(); UserBean userBean = new UserBean(); userBean.setLoginName(s); UserBean userInfo = userService.getUserInfo(userBean); //读取用户信息 if(userInfo == null) { throw new UsernameNotFoundException("username is not exist."); } UserRolesBean userRolesBean = new UserRolesBean(); userRolesBean.setUserId(userInfo.getUserId()); List rolesBeanList = userService.getRolesBeanById(userRolesBean); //根据用户id读取用户的权限角色 for(UserRolesBean tmpRoleBean : rolesBeanList) { authorities.add(new SimpleGrantedAuthority(ConstantVal.ROLE_PREFIX + tmpRoleBean.getRoleId().toString())); } User userDetails = new User(userInfo.getLoginName(),userInfo.getUserPwd(),authorities); return userDetails; } } 用户表及用户与角色对应表内容: (6)自定义MyValidCodeProcessingFilter内容:
package com.lxht.emos.security; import com.lxht.emos.utils.ConstantVal; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class MyValidCodeProcessingFilter extends AbstractAuthenticationProcessingFilter { public MyValidCodeProcessingFilter(String defaultFilterProcessesUrl) { super(defaultFilterProcessesUrl); setContinueChainBeforeSuccessfulAuthentication(true); //设置为true,否则该filter执行完毕后,将不会转至UsernamePasswordAuthenticationFilter执行,原因请查看AbstractAuthenticationProcessingFilter类的dofilter方法 } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException { String username = request.getParameter(ConstantVal.PAR_USER_NAME); String password = request.getParameter(ConstantVal.PAR_PASSWORD); String validCode = request.getParameter(ConstantVal.PAR_CHECK_CODE); valid(validCode, request.getSession()); //验证用户从前台录入的验证码是否有效 UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); return token; }
//进行验证码验证的方法 public void valid(String validCode, HttpSession session) { if (validCode == null) { throw new BadCredentialsException("验证码为空!"); } String checkCode = (String)session.getAttribute(ConstantVal.CHECK_CODE); if (!validCode.equals(checkCode)) { session.removeAttribute(ConstantVal.CHECK_CODE); throw new BadCredentialsException("验证码错误!"); } else { session.removeAttribute(ConstantVal.CHECK_CODE); } } public SimpleUrlAuthenticationFailureHandler getFailureHandler() { SimpleUrlAuthenticationFailureHandler failureHandler = (SimpleUrlAuthenticationFailureHandler)super.getFailureHandler();
//本句也必须设置为true,否则验证失败后,不会转至MySecurityConfig类中配置的failureUrl方法中 failureHandler.setUseForward(true); return failureHandler;
}
}
(7)Controller层的类 AuthValidController
package com.lxht.emos.controller; import com.lxht.emos.bean.ReturnBean; import com.lxht.emos.bean.UserBean; import com.lxht.emos.bean.UserRolesBean; import com.lxht.emos.bean.UserSessionBean; import com.lxht.emos.event.UserModifiedPublisher; import com.lxht.emos.exception.AuthException; import com.lxht.emos.service.MenuService; import com.lxht.emos.service.UserService; import com.lxht.emos.utils.ConstantVal; import com.sun.org.apache.bcel.internal.generic.RETURN; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.WebAttributes; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; @RestController public class AuthValidController { @Autowired private UserService userService; @RequestMapping("/") public UserBean authCenterHome() { User userSessionBean = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); UserBean userBean = new UserBean(); userBean.setLoginName(userSessionBean.getUsername()); UserBean userInfo = userService.getUserInfo(userBean); return userInfo; } @RequestMapping("/userLogin") public UserBean userLogin(HttpServletRequest request) throws Exception{ UserBean userInfo = new UserBean(); String username = ""; String lb = request.getParameter("error"); if("T".equals(lb)) { Exception exception = (Exception)request.getAttribute("SPRING_SECURITY_LAST_EXCEPTION"); if(exception != null) { throw new AuthException(exception.getMessage()); } else { String errMsg = "用户名或密码错误!"; throw new AuthException(errMsg); } } else { User userSessionBean = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); UserBean userBean = new UserBean(); userBean.setLoginName(userSessionBean.getUsername()); } userInfo.setLoginName(username); return userInfo; } @RequestMapping("/userAuth") public UserBean userAuth() { User userSessionBean = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); UserBean userBean = new UserBean(); userBean.setLoginName(userSessionBean.getUsername()); UserBean userInfo = userService.getUserInfo(userBean); return userInfo; } @RequestMapping("/applyCheckCode") public ReturnBean applyCheckCode(HttpServletRequest request) { ReturnBean returnBean = new ReturnBean(); request.getSession().setAttribute("CHECK_CODE","1111"); //这里只是做demo演示,没有使用插件来生成正成的图片验证码,只是简单的固定将验证码1111写入了session中 returnBean.setRetCode("1"); return returnBean; } }(8)通过以上的代码和配置,即可以完成spring security的自定义权限控制和增加验证码功能;
(10)注意,我这里使用到了cache 和session均存至redis的配置,具体的配置说明方法,请参考另一篇文章:
springboot2.0-启动cache和session同时存入redis(使用不同的数据库):
https://blog.csdn.net/fycghy0803/article/details/80482447