1.在pom.xml中增加spring security jar的引用:
<!--引入spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</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地址及权限角色
List<MenuBean> menuBeanList = 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
public UserDetailsService userDetailsService() {
MyUserDetailsService myUserDetailsService = new MyUserDetailsService(); return myUserDetailsService;
}
@Bean
public SecurityContextRepository securityContextRepository() {
//设置对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 {
ArrayList<SimpleGrantedAuthority> authorities = 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<UserRolesBean> 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的自定义权限控制和增加验证码功能;
(9)测试分两步:
第一步:http://localhost:8000/applyCheckCode 获取验证码;
第二步:访问/userLogin进行用户登录操作
(10)注意,我这里使用到了cache 和session均存至redis的配置,具体的配置说明方法,请参考另一篇文章:
springboot2.0-启动cache和session同时存入redis(使用不同的数据库):
https://blog.youkuaiyun.com/fycghy0803/article/details/80482447
(11) 系统demo源码地址:
https://github.com/fycghy0803/authCenter/tree/master
本文介绍如何在 Spring Boot 应用中使用 Spring Security 实现自定义权限控制,并结合数据库和 Redis 进行用户认证与授权。通过创建配置类、自定义 UserDetailsService 和 ValidCodeProcessingFilter,实现对 URL 的访问控制并加入验证码功能。
387

被折叠的 条评论
为什么被折叠?



