转载务必说明出处:https://blog.csdn.net/LiaoHongHB/article/details/83576911
spring security 是一种安全框架,主要的作用是认证和授权;其中认证则是判断该用户是谁,授权则是判断该用户有无权限访问该url。一个项目一般情况分为前台的业务系统(电商网站)和后台管理系统;其中前台的业务系统主要操作对象为普通用户,角色相对少,权限也比较固定,所以在前台的业务系统中一般不采用spring security;后台管理系统则主要是由内部的管理员对前台的业务系统进行相关操作,涉及到的角色较多,权限也比较复杂,所以我们一般在后台管理系统中采用spring security。
接下来就通过spring boot整合一个基本的spring security。
1、首先导入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、创建一个SecurityConfig文件,继承WebSecurityConfigureAdapter,重写其中的一个config方法
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private MyAuthenticationFailureHander myAuthenticationFailureHander;
@Autowired
private SecurityProperites securityProperites;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//跳转的登陆页面action
.loginPage("/login")
//登陆页面的action
.loginProcessingUrl("/authentication/login")
//登陆失败的页面url
.failureUrl("/login-error.jsp")
.permitAll()
//登陆成功的处理器
.successHandler(myAuthenticationSuccessHandler)
//登陆失败的处理器
.failureHandler(myAuthenticationFailureHander)
.and()
.authorizeRequests()
//表示antMatchers括号中的url不需要权限就可以访问
.antMatchers("/login",
"/login-error.jsp",
"/authentication/login",
"/demo-signIn.jsp",
securityProperites.getBrowserProperites().getLoginPage()).permitAll()
//表示除了上述的url之外,其他的url访问都需要权限认证
.anyRequest()
//使用rbac框架来做权限认证处理
.access("@rbacService.hasPermission(request,authentication)")
.and()
.csrf().disable();
}
3、创建User类,实现序列化接口和UserDetail对象
public class User implements Serializable,UserDetails {
private static final long serialVersionUID = -2279654576873280156L;
private String username;
private String password;
private String role;
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
}
@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 true;
}
}
4、新建LoginController并写一个login方法
@RestController
public class LoginController {
private Logger logger = LoggerFactory.getLogger(LoginController.class);
private RequestCache requestCache = new HttpSessionRequestCache();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Autowired
private SecurityProperites securityProperites;
@RequestMapping("/login")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public RestFulVO login(HttpServletRequest request, HttpServletResponse response) throws IOException {
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest != null) {
// 获取引发授权跳转的请求地址
String targetUrl = savedRequest.getRedirectUrl();
logger.info("此次访问【" + targetUrl + "】没有授权,需要跳转到授权页面");
if (StringUtils.endsWithIgnoreCase(targetUrl,".jsp")) {
redirectStrategy.sendRedirect(request,response,securityProperites.getBrowserProperites().getLoginPage());
}
}
return new RestFulVO("访问的服务需要身份认证");
}
如果发起的请求是以.jsp结尾的,则返回到登陆页面;否则直接返回一个json类型的数据“访问的服务需要身份认证”
5、新建UserService对象,继承UserDetailService,重写其中的loadUserByUsername方法,判断用户输入的username和 password是否正确
public interface UserService extends UserDetailsService {
@Override
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private PasswordEncoder passwordEncoder;
// @Autowired
// private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
/*
在实际开发过程中这里本来是应该通过调用userMapper中的方法判断用户输入的username和password和数据库中的数据是否一致
但是为了说明方便,直接new User对象
*/
System.out.println("username: " + username);
String password = passwordEncoder.encode("123456");
System.out.println("password: " + password);
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.getAuthorities();
return user;
// return new org.springframework.security.core.userdetails.User(username,password,AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin"));
}
}
6、创建MyAuthenticationSuccessHandler,自定义登陆成功的处理器
@Component("myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(MyAuthenticationSuccessHandler.class);
@Autowired
private ObjectMapper objectMapper;
@Autowired
private SecurityProperites securityProperites;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
logger.info("登陆成功......");
//这里可以根据实际情况,来确定是跳转到页面或者json格式。
if (LoginType.JSON.equals(securityProperites.getBrowserProperites().getLoginType())) {
//如果是返回json格式,那么我们这么写
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
} else {
super.onAuthenticationSuccess(request,response,authentication);
}
}
}
7、创建MyAuthenticationFailureHandler,自定义登陆失败的处理器
@Component("myAuthenticationFailureHander")
public class MyAuthenticationFailureHander extends SimpleUrlAuthenticationFailureHandler {
private Logger logger = LoggerFactory.getLogger(MyAuthenticationFailureHander.class);
@Autowired
private ObjectMapper objectMapper;
@Autowired
private SecurityProperites securityProperites;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
logger.info("登陆失败");
if (LoginType.JSON.equals(securityProperites.getBrowserProperites().getLoginType())) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(exception));
} else {
super.onAuthenticationFailure(request,response,exception);
}
}
}
8、创建RbacService接口,定义一个接口方法,然后创建RbacServiceImpl实现该接口并重写接口方法,以达到对用户进行权限认证的功能
public interface RbacService {
boolean hasPermission(HttpServletRequest request, Authentication authentication);
}
@Component("rbacService")
public class RbacServiceImpl implements RbacService {
private static Logger logger = LoggerFactory.getLogger(RbacServiceImpl.class);
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object principal = authentication.getPrincipal();
boolean hasPermission = false;
if (principal instanceof UserDetails) { //首先判断先当前用户是否是我们UserDetails对象。
Set<String> urls = new HashSet<>(); // 数据库读取 //读取用户所拥有权限的所有URL
// 注意这里不能用equal来判断,因为有些URL是有参数的,所以要用AntPathMatcher来比较
System.out.println(request.getRequestURI());
for (String url : urls) {
if (antPathMatcher.match(url, request.getRequestURI())) {
hasPermission = true;
break;
}
}
if (!hasPermission) {
logger.warn("该用户{}没有{}访问权限", principal, request.getRequestURI());
}
}
return hasPermission;
}
}
在hasPermission方法实现中,应该根据该用户的id来查找该用户所有权限的url集合,然后通过match方法将该用户现在访问的url地址(request.getRequestURI)与url集合进行匹配,如果匹配成功,则说明该用户又权限访问该url,返回true;否则返回false;在实际开发过程中,这里也应该使用Mapper根据userId查找数据库获取该userId下的所有可访问的url集合。
9、启动测试
启动该项目,并在地址栏输入localhost:5001/index.jsp
由于index.jsp不在antMatches中,所以首先会在RbacServiceImpl中判断该用户是否有权限访问
.authorizeRequests()
//表示antMatchers括号中的url不需要权限就可以访问
.antMatchers("/login",
"/login-error.jsp",
"/authentication/login",
"/demo-signIn.jsp",
securityProperites.getBrowserProperites().getLoginPage()).permitAll()
//表示除了上述的url之外,其他的url访问都需要权限认证
.anyRequest()
如果返回true,这表示该用户拥有访问index.jsp的权限;如果返回false则表示该没有没有权限访问index.jsp,接下来跳到LoginAction中login方法进行判断,由于访问的index.jsp是以.jsp结尾的,也就是说是页面,则再跳转到授权页面,也就是登陆页面,用户在输入用户名和密码之后,点击登陆,由于登陆的action为“/authentication/login”不需要权限,所以直接跳转到UserServiceImpl中验证用户信息是否正确,如果登陆失败则通过自定义的FailureHandle返回相关信息,如果登陆成功,则跳转到RbacServiceImpl中重新验证该用户有无权限,如果true则显示index.jsp相关信息,如果false则显示该用户没有权限(access deny)。
没有权限的页面: