一、Spring Security介绍
1、框架介绍
Spring 是一个非常流行和成功的 Java 应用开发框架。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的形式进行授权与认证。 1、用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值 2、并以用户名为key,权限列表为value的形式存入redis缓存中 3、根据用户名相关信息生成token返回 4、浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中 5、Spring-security解析header头获取token信息,解析token获取当前用户名 6、根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问
二、 整合Spring Security
1、在common下创建spring_security模块
2、在spring_security引入相关依赖
<dependencies>
<dependency>
<groupId>com
.atguigu
</groupId
>
<artifactId>common_utils
</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
>
3、复制工具类到common_utils
3、spring security核心代码
3.1、创建spring security核心配置类—TokenWebSecurityConfig
Spring Security的核心配置就是继承WebSecurityConfigurerAdapter并注解**@EnableWebSecurity**的配置。
这个配置指明了用户名密码的处理方式、请求路径的开合、登录登出控制等和安全相关的配置
package com
.atguigu
.serurity
.config
;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled
= true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService
;
private TokenManager tokenManager
;
private DefaultPasswordEncoder defaultPasswordEncoder
;
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
;
}
@Override
protected void configure(HttpSecurity http
) throws Exception
{
http
.exceptionHandling()
.authenticationEntryPoint(new UnauthorizedEntryPoint())
.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 TokenAuthenticationFilter(authenticationManager(), tokenManager
, redisTemplate
)).httpBasic();
}
@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/**",
"/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
);
}
}
3.2、创建认证授权相关的工具类
(1)DefaultPasswordEncoder:密码处理的方法
package com
.atguigu
.serurity
.security
;
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
public DefaultPasswordEncoder() {
this(-1);
}
public DefaultPasswordEncoder(int strength
) {
}
public String
encode(CharSequence rawPassword
) {
return MD5
.encrypt(rawPassword
.toString());
}
public boolean matches(CharSequence rawPassword
, String encodedPassword
) {
return encodedPassword
.equals(MD5
.encrypt(rawPassword
.toString()));
}
}
(2)TokenManager:token操作的工具类
package com
.atguigu
.serurity
.security
;
@Component
public class TokenManager {
private long tokenExpiration
= 24*60*60*1000;
private String tokenSignKey
= "123456";
public String
createToken(String username
) {
String token
= Jwts
.builder().setSubject(username
)
.setExpiration(new Date(System
.currentTimeMillis() + tokenExpiration
))
.signWith(SignatureAlgorithm
.HS512
, tokenSignKey
).compressWith(CompressionCodecs
.GZIP
).compact();
return token
;
}
public String
getUserFromToken(String token
) {
String user
= Jwts
.parser().setSigningKey(tokenSignKey
).parseClaimsJws(token
).getBody().getSubject();
return user
;
}
public void removeToken(String token
) {
}
}
(3)TokenLogoutHandler:退出实现
package com
.atguigu
.serurity
.security
;
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
) {
String token
= request
.getHeader("token");
if (token
!= null
) {
tokenManager
.removeToken(token
);
String userName
= tokenManager
.getUserFromToken(token
);
redisTemplate
.delete(userName
);
}
ResponseUtil
.out(response
, R
.ok());
}
}
(4)UnauthorizedEntryPoint:未授权统一处理
package com
.atguigu
.serurity
.security
;
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request
, HttpServletResponse response
,
AuthenticationException authException
) throws IOException
, ServletException
{
ResponseUtil
.out(response
, R
.error());
}
}
4、在service_acl引入spring_security依赖
<dependency>
<groupId>com
.atguigu
</groupId
>
<artifactId>spring_security
</artifactId
>
<version>0.0.1-SNAPSHOT
</version
>
</dependency
>
5、创建查询登录和用户权限类
创建认证授权实体类—SecutityUser
package com
.atguigu
.serurity
.entity
;
@Data
@Slf4j
public class SecurityUser implements UserDetails {
private transient User currentUserInfo
;
private List
<String> permissionValueList
;
public SecurityUser() {
}
public SecurityUser(User 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;
}
}
User
package com
.atguigu
.serurity
.entity
;
@Data
@ApiModel(description
= "用户实体类")
public class User 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
;
}
创建认证和授权的filter
(1)TokenLoginFilter:认证的filter
package com
.atguigu
.serurity
.filter
;
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);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
}
@Override
public Authentication
attemptAuthentication(HttpServletRequest req
, HttpServletResponse res
)
throws AuthenticationException
{
try {
User user
= new ObjectMapper().readValue(req
.getInputStream(), User
.class);
return authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(user
.getUsername(), user
.getPassword(), new ArrayList<>()));
} catch (IOException e
) {
throw new RuntimeException(e
);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest req
, HttpServletResponse res
, FilterChain chain
,
Authentication auth
) throws IOException
, ServletException
{
SecurityUser user
= (SecurityUser
) auth
.getPrincipal();
String token
= tokenManager
.createToken(user
.getCurrentUserInfo().getUsername());
redisTemplate
.opsForValue().set(user
.getCurrentUserInfo().getUsername(), user
.getPermissionValueList());
ResponseUtil
.out(res
, R
.ok().data("token", token
));
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request
, HttpServletResponse response
,
AuthenticationException e
) throws IOException
, ServletException
{
ResponseUtil
.out(response
, R
.error());
}
}
(2)TokenAuthenticationFilter:
授权filter
package com
.atguigu
.serurity
.filter
;
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
private TokenManager tokenManager
;
private RedisTemplate redisTemplate
;
public TokenAuthenticationFilter(AuthenticationManager authManager
, TokenManager tokenManager
,RedisTemplate redisTemplate
) {
super(authManager
);
this.tokenManager
= tokenManager
;
this.redisTemplate
= redisTemplate
;
}
@Override
protected void doFilterInternal(HttpServletRequest req
, HttpServletResponse res
, FilterChain chain
)
throws IOException
, ServletException
{
logger
.info("================="+req
.getRequestURI());
if(req
.getRequestURI().indexOf("admin") == -1) {
chain
.doFilter(req
, res
);
return;
}
UsernamePasswordAuthenticationToken authentication
= null
;
try {
authentication
= getAuthentication(req
);
} catch (Exception e
) {
ResponseUtil
.out(res
, R
.error());
}
if (authentication
!= null
) {
SecurityContextHolder
.getContext().setAuthentication(authentication
);
} else {
ResponseUtil
.out(res
, R
.error());
}
chain
.doFilter(req
, res
);
}
private UsernamePasswordAuthenticationToken
getAuthentication(HttpServletRequest request
) {
String token
= request
.getHeader("token");
if (token
!= null
&& !"".equals(token
.trim())) {
String userName
= tokenManager
.getUserFromToken(token
);
List
<String> permissionValueList
= (List
<String>) redisTemplate
.opsForValue().get(userName
);
Collection
<GrantedAuthority> authorities
= new ArrayList<>();
for(String permissionValue
: permissionValueList
) {
if(StringUtils
.isEmpty(permissionValue
)) continue;
SimpleGrantedAuthority authority
= new SimpleGrantedAuthority(permissionValue
);
authorities
.add(authority
);
}
if (!StringUtils
.isEmpty(userName
)) {
return new UsernamePasswordAuthenticationToken(userName
, token
, authorities
);
}
return null
;
}
return null
;
}
}