Spring 是非常流行和成功的 Java 应用开发框架,Spring Security正是Spring家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。 正如你可能知道的关于安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括**用户认证(Authentication)和用户授权(Authorization)**两个部分,这两点也是Spring Security重要核心功能。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
和Spring无缝整合。
全面的权限控制。
专门为Web开发而设计。
旧版本不能脱离Web环境使用。
新版本对整个框架进行了分层抽取,分成了核心模块和Web模块。单独引入 核心模块就可以脱离Web环境。
重量级。
SpringSecurity 本质就是一个过滤器链。
通过查看源码重点看以下三个过滤器:
FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部。
ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常
UsernamePasswordAuthenticationFilter :对/login的POST请求做拦截,校验表单中用户名,密码
1,引入Spring Security依赖
<!-- Spring Security依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2,编写用户实体类
UserDetails 是SpringSecurity 默认返回的用户主体对象。
package com.example.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(description = "用户实体类")
public class CurrentUserInfo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "微信openid")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "昵称")
private String nickName;
@ApiModelProperty(value = "用户头像")
private String salt;
@ApiModelProperty(value = "用户签名")
private String token;
}
package com.example.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Data
public class SecurityUser implements UserDetails {
//当前登录用户
private transient CurrentUserInfo currentUserInfo;
//当前权限
private List<String> permissionValueList;
public SecurityUser() {
}
public SecurityUser(CurrentUserInfo user) {
if (user != null) {
this.currentUserInfo = user;
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (String permissionValue : permissionValueList) {
if (StringUtils.isEmpty(permissionValue)) {
continue;
}
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
return authorities;
}
@Override
public String getPassword() {
return currentUserInfo.getPassword();
}
@Override
public String getUsername() {
return currentUserInfo.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
3,编写认证过滤器
package com.example.filer;
import com.example.entity.CurrentUserInfo;
import com.example.entity.SecurityUser;
import com.example.security.TokenManager;
import com.example.utils.R;
import com.example.utils.ResponseUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
/**
* 认证过滤器 生成token 放到redis中
*/
@SuppressWarnings("unchecked")
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
/**
* 权限
*/
private AuthenticationManager authenticationManager;
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
this.authenticationManager = authenticationManager;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
this.setPostOnly(false);
// 设置登陆路径,并且post请求
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/sys/login", "POST"));
}
/**
* 获取表单提交的相关信息
*
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 获取表单提交数据
String loginName = request.getParameter("loginName");
String loginPwd = request.getParameter("loginPwd");
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginName, loginPwd, new ArrayList<>()));
}
/**
* Default behaviour for successful authentication.
* 认证成功调用的方法 生成token 存入到redis
*
* @param request
* @param response
* @param chain
* @param authResult the object returned from the <tt>attemptAuthentication</tt>
* method.
* @throws IOException
* @throws ServletException
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// 认证成功之后,得到认证成功后的用户信息
SecurityUser user = (SecurityUser) authResult.getPrincipal();
// 根据用户名生成token
String token = tokenManager.createToken(user.getUsername());
// 把用户名和用户权限放入redis中
redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
ResponseUtil.out(response, R.ok().data("token", token));
}
/**
* Default behaviour for unsuccessful authentication.
* 认证失败调用的方法
*
* @param request
* @param response
* @param failed
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}
4,编写授权过滤器
package com.example.filer;
import com.example.security.TokenManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
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.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* 授权过滤器
*
*/
@SuppressWarnings("unchecked")
public class TokenAuthFilter extends BasicAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenAuthFilter(AuthenticationManager authenticationManager,
TokenManager tokenManager,
RedisTemplate redisTemplate) {
super(authenticationManager);
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 获认证成功的用户权限信息
UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);
// 有权限信息 放入权限上下文中
if (authRequest != null) {
SecurityContextHolder.getContext().setAuthentication(authRequest);
}
chain.doFilter(request, response);
}
/**
* 从token获取用户名,从redis获取对应权限列表,
*/
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
//从header获取token
String token = request.getHeader("token");
if (token == null) {
return null;
}
//从token获取用户名
String username = tokenManager.getUserInfoFromToken(token);
//从redis获取对应权限列表
List<String> list = (List<String>) redisTemplate.opsForValue().get(username);
if (null == list) {
return null;
}
Collection<GrantedAuthority> authorities = new ArrayList<>();
list.forEach(item -> {
authorities.add(new SimpleGrantedAuthority(item));
});
return new UsernamePasswordAuthenticationToken(username, token, authorities);
}
}
5,编写密码处理工具
package com.example.security;
import com.example.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
* 密码处理工具类
*
*/
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
public DefaultPasswordEncoder() {
this(-1);
}
public DefaultPasswordEncoder(int strength) {
}
/**
* 进行MD5加密
*/
@Override
public String encode(CharSequence rawPassword) {
return MD5.encrypt(rawPassword.toString());
}
/**
* 进行密码比对
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
}
}
6,编写退出登录处理器
package com.example.security;
import com.example.utils.R;
import com.example.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 退出处理器 退出时移除token 并删除redis中的token信息
*/
public class TokenLogoutHandler implements LogoutHandler {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
//1 从header里面获取token
//2 token不为空,移除token,从redis删除token
String token = request.getHeader("token");
if (token != null) {
//移除
tokenManager.removeToken(token);
//从token获取用户名
String username = tokenManager.getUserInfoFromToken(token);
redisTemplate.delete(username);
}
ResponseUtil.out(response, R.ok());
}
}
7,编写未授权统一处理类
package com.example.security;
import com.example.utils.R;
import com.example.utils.ResponseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 未授权统一处理类
*/
public class UnauthEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
ResponseUtil.out(httpServletResponse, R.error());
}
}
8,编写Spring Security 配置类
package com.example.config;
import com.example.filer.TokenAuthFilter;
import com.example.filer.TokenLoginFilter;
import com.example.security.DefaultPasswordEncoder;
import com.example.security.TokenLogoutHandler;
import com.example.security.TokenManager;
import com.example.security.UnauthEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.core.userdetails.UserDetailsService;
@Configuration
@EnableWebSecurity
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private DefaultPasswordEncoder defaultPasswordEncoder;
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
@Autowired
public TokenWebSecurityConfig(UserDetailsService userDetailsService,
DefaultPasswordEncoder defaultPasswordEncoder,
TokenManager tokenManager,
RedisTemplate redisTemplate) {
this.userDetailsService = userDetailsService;
this.defaultPasswordEncoder = defaultPasswordEncoder;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
/**
* 配置设置
*
* @param http
* @throws Exception
*/
//设置退出的地址和token,redis操作地址
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
//没有权限访问时调用自定义的处理类
.authenticationEntryPoint(new UnauthEntryPoint())
.and().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
//退出路径
.and()
.logout().logoutUrl("/admin/acl/index/logout")
// 调用退出时的处理器
.addLogoutHandler(new TokenLogoutHandler(tokenManager, redisTemplate))
.and()
// 认证过滤器
.addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
// 授权过滤器
.addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate))
.httpBasic();
}
/**
* 调用userDetailsService和密码处理
*
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
}
/**
* 不进行认证的路径,可以直接访问
*
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/**");
}
}
9,编写userDetailsService 查询用户信息
package com.example.service.impl;
import com.example.entity.CurrentUserInfo;
import com.example.entity.SecurityUser;
import com.example.entity.User;
import org.springframework.beans.BeanUtils;
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 javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
// @Resource
// private UserService userService;
//
// @Resource
// private PermissionService permissionService;
/**
* 根据用户名 获取用户权限
*
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名查询数据
User user = null;
// User user = userService.selectByUsername(username);
//判断
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
CurrentUserInfo curUser = new CurrentUserInfo();
BeanUtils.copyProperties(user, curUser);
//
List<String> permissionValueList = new ArrayList<>();
// List<String> permissionValueList = permissionService.selectPermissionValueByUserId(user.getId());
SecurityUser securityUser = new SecurityUser();
securityUser.setCurrentUserInfo(curUser);
securityUser.setPermissionValueList(permissionValueList);
return securityUser;
}
}
10,Spring Secuirity的调用过程
登录时—>
TokenLoginFilter->UserDetailsServiceImpl->DefaultPasswordEncoder->再到登录成功或者失败的方法
访问方法时—>
TokenAuthFilter->认证通过后到具体的方法
11,Spring Secuirity 的授权过程 还是需要靠后台业务代码实现