Spring Security 核心组件有:
- SecurityContext:security安全上下文,通过校验之后,验证信息存储 SecurityContext中。
- SecurityContextHolder:存储认证信息。
- Authentication:存储当前用户。
- UserDetails:用户信息。
- AuthenticationManager:作用就是校验Authentication,如果验证失败会抛出AuthenticationException异常。
框架的认证过程:
用户名密码->Authentication(未认证) -> AuthenticationManager->AuthenticationProvider->UserDetailService->UserDetails->Authentication(已认证)
所以步骤大致有:
第一步:我们定义自己的用户信息类 UserInfo 继承UserDetails和Serializable接口
package com.sjh.security;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.Collection;
/**
* 用户信息bean
*/
public class UserInfo implements UserDetails, Serializable{
private static final long serialVersionUID = 8155405987900665123L;
private String username;
private String password;
private String role;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;
public UserInfo(String username, String password, String role, boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired, boolean enabled) {
this.username = username;
this.password = password;
this.role = role;
this.accountNonExpired = accountNonExpired;
this.accountNonLocked = accountNonLocked;
this.credentialsNonExpired = credentialsNonExpired;
this.enabled = enabled;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.commaSeparatedStringToAuthorityList(role);
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public String toString() {
return "UserInfo{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", role='" + role + '\'' +
", accountNonExpired=" + accountNonExpired +
", accountNonLocked=" + accountNonLocked +
", credentialsNonExpired=" + credentialsNonExpired +
", enabled=" + enabled +
'}';
}
}
第二步:写一个UserDetailsService接口的实现类来返回这个UserInfo的对象实例
package com.sjh.security;
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.Component;
/**
* 加载用户信息,根据用户名
*/
@Component
public class MyUserDetailsService implements UserDetailsService{
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//这里可以可以通过s(登录时输入的用户名)然后到数据库中找到对应的用户信息,并构建成我们自己的UserInfo来返回。
if("admin".equals(s)){
return new UserInfo("admin", "123456", "ROLE_ADMIN", true,true,true, true);
}
return null;
}
}
第三步:实现我们自己的 AuthenticationProvider,新建类 MyAuthenticationProvider 继承AuthenticationProvider
package com.sjh.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
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.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.Collection;
/**
*验证登录
*/
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
//注入自己的UserDetailsService
@Autowired
private MyUserDetailsService myUserDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = authentication.getName();// 这个获取表单输入中返回的用户名;
System.out.println(userName);
String password = (String) authentication.getCredentials();// 这个是表单中输入的密码;
UserInfo userInfo = (UserInfo) myUserDetailsService.loadUserByUsername(userName);
if(userInfo==null){
throw new BadCredentialsException("用户名不存在");
}
if (!userInfo.getPassword().equals(password)) {
throw new BadCredentialsException("密码不正确");
}
Collection<? extends GrantedAuthority> authorities = userInfo.getAuthorities();
// 构建返回的用户登录成功的token
return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
}
@Override
public boolean supports(Class<?> aClass) {
// 支持执行
return true;
}
}
第四步: 自定义登录成功和失败的处理逻辑( 分别继承SavedRequestAwareAuthenticationSuccessHandler和SimpleUrlAuthenticationFailureHandler2个类,并重写其中的部分方法即可。)
package com.sjh.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录成功
*/
@Component
public class MySavedRequestAwareAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
private Logger logger = LoggerFactory.getLogger(getClass());
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
//什么都不做的话,直接调用父类的方法
super.onAuthenticationSuccess(request, response, authentication);
//如果是返回json格式,那么我们这么写
// logger.info("登录成功");
// Map<String,String> map=new HashMap<>();
// map.put("code", "200");
// map.put("msg", "登录成功");
// response.setContentType("application/json;charset=UTF-8");
// response.getWriter().write(objectMapper.writeValueAsString(map));
}
package com.sjh.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 登录失败
*/
@Component
public class MySimpleUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
private Logger logger = LoggerFactory.getLogger(getClass());
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
logger.info("登录失败");
//以Json格式返回
Map<String,String> map=new HashMap<>();
map.put("code", "201");
map.put("msg", "登录失败");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(map));
}
}
RBAC(role-Based-access control)权限控制
一个是用户,一个是角色 ,一个是资源(菜单,按钮),其中用户和角色关联,角色和资源关联
/**
* 根据登录的用户,添加权限
*/
@Component("rbacService")
public class RbacServiceImpl implements RbacService{
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对象。
String userName = ((UserDetails) principal).getUsername();
Set<String> urls = new HashSet<>(); // 数据库读取 //读取用户所拥有权限的所有URL
urls.add("/admin/aaa");
urls.add("/user/getusersbyid");
// 注意这里不能用equal来判断,因为有些URL是有参数的,所以要用AntPathMatcher来比较
for (String url : urls) {
if (antPathMatcher.match(url, request.getRequestURI())) {
hasPermission = true;
break;
}
}
}
return hasPermission;
}
}
配置
package com.sjh.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
/**
* 配置类
*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationProvider provider;
@Autowired
private MySavedRequestAwareAuthenticationSuccessHandler mySavedRequestAwareAuthenticationSuccessHandler;
@Autowired
private MySimpleUrlAuthenticationFailureHandler mySimpleUrlAuthenticationFailureHandler;
@Autowired
private DataSource dataSource;
/**
* 密码的加密
* @return
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 添加用户以及用户的权限
*
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth
// .inMemoryAuthentication()
// .withUser("admin") // 管理员,同事具有 ADMIN,USER权限,可以访问所有资源
// .password(passwordEncoder().encode("123"))
// .roles("ADMIN", "USER")
// .and()
// .withUser("user1")
// .password(passwordEncoder().encode("123")) // 普通用户,只能访问
// .roles("USER");
auth.authenticationProvider(provider);
}
/**
* authorizeRequests() 定义哪些URL需要被保护、哪些不需要被保护。
* formLogin() 定义当需要用户登录时候,转到的登录页面。
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login/form")
// .failureUrl("/login-error")
.successHandler(mySavedRequestAwareAuthenticationSuccessHandler)
.failureHandler(mySimpleUrlAuthenticationFailureHandler)
.permitAll() //表单登录,permitAll()表示这个不需要验证
.and()
.authorizeRequests()
.anyRequest().access("@rbacService.hasPermission(request,authentication)")
.and()
.csrf().disable();
// .antMatchers("/user/**").hasRole("USER")
// .antMatchers("/admin/**").hasRole("ADMIN")
// .anyRequest().authenticated()
}
/**
*忽略拦截
*/
@Override
public void configure(WebSecurity web) throws Exception {
// web.ignoring().antMatchers("/admin/aaa");
}
}
页面
页面的目录
userlogin.html如下:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>登录</title>
</head>
<body>
<div class="container">
<form method="post" action="/login/form">
<h2>Please sign in</h2>
<p>
<label>Username</label>
<input type="text" name="username" placeholder="请输入用户名"/>
</p>
<p>
<label>Password</label>
<input type="password" name="password" class="form-control" placeholder="请输入密码" />
</p>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
控制类
package com.sjh.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@Controller
public class LoginController {
@GetMapping("/login")
public String login() {
return "userlogin";
}
// @GetMapping("/login/form")
// @ResponseBody
// public String loginfrom() {
// return "sssss";
// }
// @RequestMapping("/login-error")
// public String loginError(){
// return "login-error";
// }
}