1、框架介绍
Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般
来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
Spring Security其实就是用filter,多请求的路径进行过滤。
(1)如果是基于Session,那么Spring-security会对cookie里的sessionid进行解析,找到服务器存储的sesion信息,然后判断当前用户是否符合请求的要求。
(2)如果是token,则是解析出token,然后将当前请求加入到Spring-security管理的权限信息中去
2、认证与授权实现思路
如果系统的模块众多,每个模块都需要就行授权与认证,所以我们选择基于token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为
key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中,Spring-security解析
header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问
3. 配置与使用
1.依赖引入

<dependencies> <dependency> <groupId>com.atguigu</groupId> <artifactId>common-util</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!-- Spring Security依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency> </dependencies>
2. 各类功能介绍
1. security

1 import com.atguigu.commonutils.MD5; 2 import org.springframework.security.crypto.password.PasswordEncoder; 3 import org.springframework.stereotype.Component; 4 5 /** 6 * <p> 7 * t密码的处理方法类型 8 * </p> 9 * 10 */ 11 @Component 12 public class DefaultPasswordEncoder implements PasswordEncoder { 13 14 public DefaultPasswordEncoder() { 15 this(-1); 16 } 17 18 /** 19 * @param strength 20 * the log rounds to use, between 4 and 31 21 */ 22 public DefaultPasswordEncoder(int strength) { 23 24 } 25 26 public String encode(CharSequence rawPassword) { 27 return MD5.encrypt(rawPassword.toString()); 28 } 29 30 public boolean matches(CharSequence rawPassword, String encodedPassword) { 31 return encodedPassword.equals(MD5.encrypt(rawPassword.toString())); 32 } 33 }

1 import com.atguigu.commonutils.R; 2 import com.atguigu.commonutils.ResponseUtil; 3 import org.springframework.data.redis.core.RedisTemplate; 4 import org.springframework.security.core.Authentication; 5 import org.springframework.security.web.authentication.logout.LogoutHandler; 6 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 /** 11 * <p> 12 * 登出业务逻辑类 13 * </p> 14 * 15 */ 16 public class TokenLogoutHandler implements LogoutHandler { 17 18 private TokenManager tokenManager; 19 private RedisTemplate redisTemplate; 20 21 public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) { 22 this.tokenManager = tokenManager; 23 this.redisTemplate = redisTemplate; 24 } 25 26 @Override 27 public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { 28 String token = request.getHeader("token"); 29 if (token != null) { 30 tokenManager.removeToken(token); // 该行其实没有处理 31 32 //清空当前用户缓存中的权限数据 33 String userName = tokenManager.getUserFromToken(token); 34 redisTemplate.delete(userName); 35 } 36 37 ResponseUtil.out(response, R.ok()); 38 } 39 40 }

1 import io.jsonwebtoken.CompressionCodecs; 2 import io.jsonwebtoken.Jwts; 3 import io.jsonwebtoken.SignatureAlgorithm; 4 import org.springframework.stereotype.Component; 5 6 import java.util.Date; 7 8 /** 9 * <p> 10 * token管理 11 * </p> 12 * 13 */ 14 @Component 15 public class TokenManager { 16 17 private long tokenExpiration = 24*60*60*1000; 18 private String tokenSignKey = "123456"; 19 20 public String createToken(String username) { 21 String token = Jwts.builder().setSubject(username) 22 .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)) 23 .signWith(SignatureAlgorithm.HS512, tokenSignKey) 24 .compressWith(CompressionCodecs.GZIP) 25 .compact(); 26 return token; 27 } 28 29 public String getUserFromToken(String token) { 30 String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject(); 31 return user; 32 } 33 34 public void removeToken(String token) { 35 //jwttoken无需删除,客户端扔掉即可。 36 } 37 38 }

1 import com.atguigu.commonutils.R; 2 import com.atguigu.commonutils.ResponseUtil; 3 import org.springframework.security.core.AuthenticationException; 4 import org.springframework.security.web.AuthenticationEntryPoint; 5 6 import javax.servlet.ServletException; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 import java.io.IOException; 10 11 /** 12 * <p> 13 * 未授权的统一处理方式 14 * </p> 15 * 16 */ 17 public class UnauthorizedEntryPoint implements AuthenticationEntryPoint { 18 19 @Override 20 public void commence(HttpServletRequest request, HttpServletResponse response, 21 AuthenticationException authException) throws IOException, ServletException { 22 23 ResponseUtil.out(response, R.error()); 24 } 25 }
2.filter

1 import com.atguigu.commonutils.R; 2 import com.atguigu.commonutils.ResponseUtil; 3 import com.atguigu.serurity.security.TokenManager; 4 import org.springframework.data.redis.core.RedisTemplate; 5 import org.springframework.security.authentication.AuthenticationManager; 6 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 import org.springframework.security.core.GrantedAuthority; 8 import org.springframework.security.core.authority.SimpleGrantedAuthority; 9 import org.springframework.security.core.context.SecurityContextHolder; 10 import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 11 import org.springframework.util.StringUtils; 12 13 import javax.servlet.FilterChain; 14 import javax.servlet.ServletException; 15 import javax.servlet.http.HttpServletRequest; 16 import javax.servlet.http.HttpServletResponse; 17 import java.io.IOException; 18 import java.util.ArrayList; 19 import java.util.Collection; 20 import java.util.List; 21 22 /** 23 * <p> 24 * 访问过滤器 25 * </p> 26 * 27 */ 28 public class TokenAuthenticationFilter extends BasicAuthenticationFilter { 29 private TokenManager tokenManager; 30 private RedisTemplate redisTemplate; 31 32 public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) { 33 super(authManager); 34 this.tokenManager = tokenManager; 35 this.redisTemplate = redisTemplate; 36 } 37 38 @Override 39 protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) 40 throws IOException, ServletException { 41 // System.out.println("=================<"+req.getRequestURI()); 42 // System.out.println("===============token:" + req.getHeader("token")); 43 if(req.getRequestURI().indexOf("admin") == -1) { 44 chain.doFilter(req, res); 45 return; 46 } 47 48 UsernamePasswordAuthenticationToken authentication = null; 49 try { 50 authentication = getAuthentication(req); 51 } catch (Exception e) { 52 ResponseUtil.out(res, R.error()); 53 } 54 55 if (authentication != null) { 56 System.out.println(authentication.getPrincipal()); 57 SecurityContextHolder.getContext().setAuthentication(authentication); 58 } else { 59 ResponseUtil.out(res, R.error()); 60 } 61 chain.doFilter(req, res); 62 } 63 64 private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { 65 // token置于header里 66 String token = request.getHeader("token"); 67 // System.out.println("token=======>" + token); 68 // token不为空或空字符串 69 if (token != null && !"".equals(token.trim())) { 70 String userName = tokenManager.getUserFromToken(token); 71 72 List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName); 73 Collection<GrantedAuthority> authorities = new ArrayList<>(); 74 for(String permissionValue : permissionValueList) { 75 if(StringUtils.isEmpty(permissionValue)) continue; 76 SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue); 77 authorities.add(authority); 78 } 79 80 81 if (!StringUtils.isEmpty(userName)) { 82 83 return new UsernamePasswordAuthenticationToken(userName, token, authorities); 84 } 85 return null; 86 } 87 return null; 88 } 89 }

1 import com.atguigu.commonutils.R; 2 import com.atguigu.commonutils.ResponseUtil; 3 import com.atguigu.serurity.entity.SecurityUser; 4 import com.atguigu.serurity.entity.User; 5 import com.atguigu.serurity.security.TokenManager; 6 import com.fasterxml.jackson.databind.ObjectMapper; 7 import org.springframework.data.redis.core.RedisTemplate; 8 import org.springframework.security.authentication.AuthenticationManager; 9 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 10 import org.springframework.security.core.Authentication; 11 import org.springframework.security.core.AuthenticationException; 12 import org.springframework.security.core.context.SecurityContextHolder; 13 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 14 import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 15 16 import javax.servlet.FilterChain; 17 import javax.servlet.ServletException; 18 import javax.servlet.http.Cookie; 19 import javax.servlet.http.HttpServletRequest; 20 import javax.servlet.http.HttpServletResponse; 21 import java.io.IOException; 22 import java.util.ArrayList; 23 24 /** 25 * <p> 26 * 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验 27 * </p> 28 * 29 */ 30 public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter { 31 32 private AuthenticationManager authenticationManager; 33 private TokenManager tokenManager; 34 private RedisTemplate redisTemplate; 35 36 public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) { 37 this.authenticationManager = authenticationManager; 38 this.tokenManager = tokenManager; 39 this.redisTemplate = redisTemplate; 40 this.setPostOnly(false); 41 AntPathRequestMatcher requestMatcher = new AntPathRequestMatcher("/admin/acl/login", "POST"); 42 System.out.println(requestMatcher); 43 this.setRequiresAuthenticationRequestMatcher(requestMatcher); 44 45 } 46 47 @Override 48 public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) 49 throws AuthenticationException { 50 try { 51 User user = new ObjectMapper().readValue(req.getInputStream(), User.class); 52 System.out.println(user); 53 UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()); 54 return authenticationManager.authenticate(usernamePasswordAuthenticationToken); 55 } catch (IOException e) { 56 throw new RuntimeException(e); 57 } 58 59 } 60 61 /** 62 * 登录成功 63 * @param req 64 * @param res 65 * @param chain 66 * @param auth 67 * @throws IOException 68 * @throws ServletException 69 */ 70 @Override 71 protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, 72 Authentication auth) throws IOException, ServletException { 73 74 SecurityUser user = (SecurityUser) auth.getPrincipal(); 75 String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername()); 76 redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList()); 77 res.setHeader("token",token); 78 // System.out.println("token:" + token); 79 ResponseUtil.out(res, R.ok().data("token", token)); 80 } 81 82 /** 83 * 登录失败 84 * @param request 85 * @param response 86 * @param e 87 * @throws IOException 88 * @throws ServletException 89 */ 90 @Override 91 protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, 92 AuthenticationException e) throws IOException, ServletException { 93 ResponseUtil.out(response, R.error()); 94 } 95 }
3. entity

1 import lombok.Data; 2 import lombok.extern.slf4j.Slf4j; 3 import org.springframework.security.core.GrantedAuthority; 4 import org.springframework.security.core.authority.SimpleGrantedAuthority; 5 import org.springframework.security.core.userdetails.UserDetails; 6 import org.springframework.util.StringUtils; 7 8 import java.util.ArrayList; 9 import java.util.Collection; 10 import java.util.List; 11 12 /** 13 * <p> 14 * 安全认证用户详情信息 15 * </p> 16 * 17 */ 18 @Data 19 @Slf4j 20 public class SecurityUser implements UserDetails { 21 22 //当前登录用户 transient不被序列化 23 private transient User currentUserInfo; 24 25 //当前权限 26 private List<String> permissionValueList; 27 28 public SecurityUser() { 29 } 30 31 public SecurityUser(User user) { 32 if (user != null) { 33 this.currentUserInfo = user; 34 } 35 } 36 37 @Override 38 public Collection<? extends GrantedAuthority> getAuthorities() { 39 Collection<GrantedAuthority> authorities = new ArrayList<>(); 40 for(String permissionValue : permissionValueList) { 41 if(StringUtils.isEmpty(permissionValue)) continue; 42 SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue); 43 authorities.add(authority); 44 } 45 46 return authorities; 47 } 48 49 @Override 50 public String getPassword() { 51 return currentUserInfo.getPassword(); 52 } 53 54 @Override 55 public String getUsername() { 56 return currentUserInfo.getUsername(); 57 } 58 59 @Override 60 public boolean isAccountNonExpired() { 61 return true; 62 } 63 64 @Override 65 public boolean isAccountNonLocked() { 66 return true; 67 } 68 69 @Override 70 public boolean isCredentialsNonExpired() { 71 return true; 72 } 73 74 @Override 75 public boolean isEnabled() { 76 return true; 77 } 78 }

1 import io.swagger.annotations.ApiModel; 2 import io.swagger.annotations.ApiModelProperty; 3 import lombok.Data; 4 5 import java.io.Serializable; 6 7 /** 8 * <p> 9 * 用户实体类 10 * </p> 11 * 12 */ 13 @Data 14 @ApiModel(description = "用户实体类") 15 public class User implements Serializable { 16 17 private static final long serialVersionUID = 1L; 18 19 @ApiModelProperty(value = "微信openid") 20 private String username; 21 22 @ApiModelProperty(value = "密码") 23 private String password; 24 25 @ApiModelProperty(value = "昵称") 26 private String nickName; 27 28 @ApiModelProperty(value = "用户头像") 29 private String salt; 30 31 @ApiModelProperty(value = "用户签名") 32 private String token; 33 34 }
4. config

1 import com.atguigu.serurity.filter.TokenAuthenticationFilter; 2 import com.atguigu.serurity.filter.TokenLoginFilter; 3 import com.atguigu.serurity.security.DefaultPasswordEncoder; 4 import com.atguigu.serurity.security.TokenLogoutHandler; 5 import com.atguigu.serurity.security.TokenManager; 6 import com.atguigu.serurity.security.UnauthorizedEntryPoint; 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.context.annotation.Configuration; 9 import org.springframework.data.redis.core.RedisTemplate; 10 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 11 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 12 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 13 import org.springframework.security.config.annotation.web.builders.WebSecurity; 14 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 15 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 16 import org.springframework.security.core.userdetails.UserDetailsService; 17 18 /** 19 * <p> 20 * Security配置类 21 * </p> 22 * 23 */ 24 @Configuration 25 @EnableWebSecurity 26 @EnableGlobalMethodSecurity(prePostEnabled = true) 27 public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter { 28 29 private UserDetailsService userDetailsService; 30 private TokenManager tokenManager; 31 private DefaultPasswordEncoder defaultPasswordEncoder; 32 private RedisTemplate redisTemplate; 33 34 @Autowired 35 public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder, 36 TokenManager tokenManager, RedisTemplate redisTemplate) { 37 this.userDetailsService = userDetailsService; 38 this.defaultPasswordEncoder = defaultPasswordEncoder; 39 this.tokenManager = tokenManager; 40 this.redisTemplate = redisTemplate; 41 } 42 43 /** 44 * 配置设置 45 * @param http 46 * @throws Exception 47 */ 48 @Override 49 protected void configure(HttpSecurity http) throws Exception { 50 http.exceptionHandling() 51 .authenticationEntryPoint(new UnauthorizedEntryPoint()) 52 .and().csrf().disable() 53 .authorizeRequests() 54 .antMatchers("/js/**","/css/**","/images/**","/fonts/**","/**/*.png","/**/*.jpg").permitAll() 55 .antMatchers("/swagger-ui.html/**","/api/**","/admin/**").permitAll() 56 .anyRequest().authenticated() 57 .and().logout().logoutUrl("/admin/acl/index/logout") 58 .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and() 59 .addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate)) 60 .addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic(); 61 } 62 63 /** 64 * 密码处理 65 * @param auth 66 * @throws Exception 67 */ 68 @Override 69 public void configure(AuthenticationManagerBuilder auth) throws Exception { 70 auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder); 71 } 72 73 /** 74 * 配置哪些请求不拦截 75 * @param web 76 * @throws Exception 77 */ 78 @Override 79 public void configure(WebSecurity web) throws Exception { 80 web.ignoring().antMatchers("/api/**", 81 "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**" 82 ); 83 // 所以都不拦截 84 // web.ignoring().antMatchers("/*/**"); 85 } 86 }
5. 需要查询数据库所获得的用户信息接口UserDetailsService 的实现类

1 import com.atguigu.aclservice.entity.User; 2 import com.atguigu.aclservice.service.PermissionService; 3 import com.atguigu.aclservice.service.UserService; 4 import com.atguigu.serurity.entity.SecurityUser; 5 import org.springframework.beans.BeanUtils; 6 import org.springframework.beans.factory.annotation.Autowired; 7 import org.springframework.security.core.userdetails.UserDetails; 8 import org.springframework.security.core.userdetails.UserDetailsService; 9 import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 import org.springframework.stereotype.Service; 11 12 import java.util.List; 13 14 15 /** 16 * <p> 17 * 自定义userDetailsService - 认证用户详情 18 * </p> 19 * 20 */ 21 @Service("userDetailsService") 22 public class UserDetailsServiceImpl implements UserDetailsService { 23 24 @Autowired 25 private UserService userService; 26 27 @Autowired 28 private PermissionService permissionService; 29 30 /*** 31 * 根据账号获取用户信息 32 * @param username: 33 * @return: org.springframework.security.core.userdetails.UserDetails 34 */ 35 @Override 36 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 37 // 从数据库中取出用户信息 38 User user = userService.selectByUsername(username); 39 System.out.println("user:" + user); 40 41 // 判断用户是否存在 42 if (null == user){ 43 //throw new UsernameNotFoundException("用户名不存在!"); 44 } 45 // 返回UserDetails实现类 46 com.atguigu.serurity.entity.User curUser = new com.atguigu.serurity.entity.User(); 47 BeanUtils.copyProperties(user,curUser); 48 49 List<String> authorities = permissionService.selectPermissionValueByUserId(user.getId()); 50 System.out.println("下面:" + authorities.get(0)); 51 SecurityUser securityUser = new SecurityUser(curUser); 52 securityUser.setPermissionValueList(authorities); 53 return securityUser; 54 } 55 56 }