From 184ecf65af82d1b2566052a21b5563906f7ff24e Mon Sep 17 00:00:00 2001 From: zwzw1219 Date: Sat, 14 Oct 2017 21:55:54 +0800 Subject: [PATCH] 1 --- pom.xml | 4 ++ .../security/server/config/RedisConfig.java | 39 ++++++++++ .../server/config/SecurityConfig.java | 15 +++- .../server/config/SecurityHandlerConfig.java | 41 ++++------- .../boot/security/server/dto/LoginUser.java | 50 +++++++++++++ .../com/boot/security/server/dto/Token.java | 25 +++++++ .../security/server/filter/TokenFilter.java | 72 +++++++++++++++++++ .../security/server/service/TokenService.java | 20 ++++++ .../server/service/impl/TokenServiceImpl.java | 54 ++++++++++++++ .../service/impl/UserDetailsServiceImpl.java | 14 ++-- .../security/server/utils/ResponseUtil.java | 23 ++++++ 11 files changed, 324 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/boot/security/server/config/RedisConfig.java create mode 100644 src/main/java/com/boot/security/server/dto/LoginUser.java create mode 100644 src/main/java/com/boot/security/server/dto/Token.java create mode 100644 src/main/java/com/boot/security/server/filter/TokenFilter.java create mode 100644 src/main/java/com/boot/security/server/service/TokenService.java create mode 100644 src/main/java/com/boot/security/server/service/impl/TokenServiceImpl.java create mode 100644 src/main/java/com/boot/security/server/utils/ResponseUtil.java diff --git a/pom.xml b/pom.xml index 85c8dac..4828816 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,10 @@ springfox-swagger-ui ${swagger.version} + + org.springframework.boot + spring-boot-starter-data-redis + diff --git a/src/main/java/com/boot/security/server/config/RedisConfig.java b/src/main/java/com/boot/security/server/config/RedisConfig.java new file mode 100644 index 0000000..0503f5d --- /dev/null +++ b/src/main/java/com/boot/security/server/config/RedisConfig.java @@ -0,0 +1,39 @@ +package com.boot.security.server.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.GenericToStringSerializer; + +/** + * redis配置
+ * 集群下启动session共享,需打开@EnableRedisHttpSession
+ * 单机下不需要 + * + * @author 小威老师 + * + * 2017年8月10日 + */ +//@EnableRedisHttpSession +@Configuration +public class RedisConfig { + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Bean("redisTemplate") + public RedisTemplate redisTemplate(@Lazy RedisConnectionFactory connectionFactory) { + RedisTemplate redis = new RedisTemplate(); + GenericToStringSerializer keySerializer = new GenericToStringSerializer(String.class); + redis.setKeySerializer(keySerializer); + redis.setHashKeySerializer(keySerializer); + GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(); + redis.setValueSerializer(valueSerializer); + redis.setHashValueSerializer(valueSerializer); + redis.setConnectionFactory(connectionFactory); + + return redis; + } + +} diff --git a/src/main/java/com/boot/security/server/config/SecurityConfig.java b/src/main/java/com/boot/security/server/config/SecurityConfig.java index 98d3cb0..eee70a0 100644 --- a/src/main/java/com/boot/security/server/config/SecurityConfig.java +++ b/src/main/java/com/boot/security/server/config/SecurityConfig.java @@ -6,11 +6,14 @@ import org.springframework.security.config.annotation.authentication.builders.Au 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.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import com.boot.security.server.filter.TokenFilter; import com.boot.security.server.service.impl.UserDetailsServiceImpl; @EnableGlobalMethodSecurity(prePostEnabled = true) @@ -24,6 +27,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { private LogoutSuccessHandler logoutSuccessHandler; @Autowired private UserDetailsServiceImpl userDetailsServiceImpl; + @Autowired + private TokenFilter tokenFilter; @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { @@ -32,14 +37,18 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { + http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class); + // 基于token,所以不需要session + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.authorizeRequests() - .antMatchers("/login.html", "/statics/**", "/v2/api-docs/**", "/swagger-resources/**", + .antMatchers("/login.html", "/static/**", "/statics/**", "/v2/api-docs/**", "/swagger-resources/**", "/swagger-ui.html", "/webjars/**") .permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login.html") .loginProcessingUrl("/login").successHandler(authenticationSuccessHandler) .failureHandler(authenticationFailureHandler).and().logout().logoutUrl("/logout") - .logoutSuccessHandler(logoutSuccessHandler).and().csrf().disable().headers().frameOptions() - .sameOrigin(); + .logoutSuccessHandler(logoutSuccessHandler); + http.csrf().disable().headers().frameOptions().sameOrigin(); + } @Override diff --git a/src/main/java/com/boot/security/server/config/SecurityHandlerConfig.java b/src/main/java/com/boot/security/server/config/SecurityHandlerConfig.java index 65587b2..56c5155 100644 --- a/src/main/java/com/boot/security/server/config/SecurityHandlerConfig.java +++ b/src/main/java/com/boot/security/server/config/SecurityHandlerConfig.java @@ -1,33 +1,35 @@ package com.boot.security.server.config; import java.io.IOException; -import java.util.stream.Collectors; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import com.alibaba.fastjson.JSONObject; +import com.boot.security.server.dto.LoginUser; import com.boot.security.server.dto.ResponseInfo; +import com.boot.security.server.dto.Token; +import com.boot.security.server.service.TokenService; +import com.boot.security.server.utils.ResponseUtil; -import lombok.extern.slf4j.Slf4j; - -@Slf4j @Configuration public class SecurityHandlerConfig { + @Autowired + private TokenService tokenService; + /** - * 登陆成功 + * 登陆成功,返回Token * * @return */ @@ -38,13 +40,10 @@ public class SecurityHandlerConfig { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - log.info(request.getRequestURI()); - User user = (User) authentication.getPrincipal(); - log.info("{}", user.getAuthorities().stream().map(a -> a.getAuthority()).collect(Collectors.toSet())); + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); - ResponseInfo info = ResponseInfo.builder().code(HttpStatus.OK.value() + "").message("登录成功").build(); - - writeResponse(response, HttpStatus.OK.value(), JSONObject.toJSONString(info)); + Token token = tokenService.saveToken(loginUser); + ResponseUtil.responseJson(response, HttpStatus.OK.value(), token); } }; } @@ -63,7 +62,7 @@ public class SecurityHandlerConfig { AuthenticationException exception) throws IOException, ServletException { ResponseInfo info = ResponseInfo.builder().code(HttpStatus.UNAUTHORIZED.value() + "") .message(exception.getMessage()).build(); - writeResponse(response, HttpStatus.UNAUTHORIZED.value(), JSONObject.toJSONString(info)); + ResponseUtil.responseJson(response, HttpStatus.UNAUTHORIZED.value(), info); } }; @@ -82,21 +81,11 @@ public class SecurityHandlerConfig { public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { ResponseInfo info = ResponseInfo.builder().code(HttpStatus.OK.value() + "").message("退出成功").build(); - writeResponse(response, HttpStatus.OK.value(), JSONObject.toJSONString(info)); + + ResponseUtil.responseJson(response, HttpStatus.OK.value(), info); } }; } - public static void writeResponse(HttpServletResponse response, int status, String json) { - try { - response.setHeader("Access-Control-Allow-Origin", "*"); - response.setHeader("Access-Control-Allow-Methods", "*"); - response.setContentType("application/json;charset=UTF-8"); - response.setStatus(status); - response.getWriter().write(json); - } catch (IOException e) { - e.printStackTrace(); - } - } } diff --git a/src/main/java/com/boot/security/server/dto/LoginUser.java b/src/main/java/com/boot/security/server/dto/LoginUser.java new file mode 100644 index 0000000..9e75b4b --- /dev/null +++ b/src/main/java/com/boot/security/server/dto/LoginUser.java @@ -0,0 +1,50 @@ +package com.boot.security.server.dto; + +import java.util.Collection; +import java.util.Set; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import com.boot.security.server.model.SysUser; + +public class LoginUser extends SysUser implements UserDetails { + + private static final long serialVersionUID = 1422037805178348848L; + + private Set authorities; + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + // 账户是否未过期 + @Override + public boolean isAccountNonExpired() { + return true; + } + + // 账户是否未锁定 + @Override + public boolean isAccountNonLocked() { + return getStatus() != Status.LOCKED; + } + + // 密码是否未过期 + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + // 账户是否激活 + @Override + public boolean isEnabled() { + return true; + } + +} diff --git a/src/main/java/com/boot/security/server/dto/Token.java b/src/main/java/com/boot/security/server/dto/Token.java new file mode 100644 index 0000000..602aa85 --- /dev/null +++ b/src/main/java/com/boot/security/server/dto/Token.java @@ -0,0 +1,25 @@ +package com.boot.security.server.dto; + +import java.io.Serializable; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +/** + * Restful方式登陆token + * + * @author 小威老师 + * + * 2017年8月4日 + */ +@Getter +@Setter +@Builder +public class Token implements Serializable { + + private static final long serialVersionUID = 6314027741784310221L; + + private String token; + +} diff --git a/src/main/java/com/boot/security/server/filter/TokenFilter.java b/src/main/java/com/boot/security/server/filter/TokenFilter.java new file mode 100644 index 0000000..51761f2 --- /dev/null +++ b/src/main/java/com/boot/security/server/filter/TokenFilter.java @@ -0,0 +1,72 @@ +package com.boot.security.server.filter; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.boot.security.server.dto.LoginUser; +import com.boot.security.server.dto.ResponseInfo; +import com.boot.security.server.service.TokenService; +import com.boot.security.server.utils.ResponseUtil; + +/** + * Token过滤器 + * + * @author 小威老师 + * + * 2017年10月14日 + */ +@Component +public class TokenFilter extends OncePerRequestFilter { + + private static final String TOKEN_KEY = "token"; + + @Autowired + private TokenService tokenService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String token = getToken(request); + if (StringUtils.isNotBlank(token)) { + LoginUser loginUser = tokenService.getLoginUser(token); + if (loginUser != null) { + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, + null, loginUser.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + + filterChain.doFilter(request, response); + } + } + + ResponseUtil.responseJson(response, HttpStatus.UNAUTHORIZED.value(), + ResponseInfo.builder().code(HttpStatus.UNAUTHORIZED.value() + "").message("token不存在").build()); + } + + /** + * 根据参数或者header获取token + * + * @param request + * @return + */ + public static String getToken(HttpServletRequest request) { + String token = request.getParameter(TOKEN_KEY); + if (StringUtils.isBlank(token)) { + token = request.getHeader(TOKEN_KEY); + } + + return token; + } + +} diff --git a/src/main/java/com/boot/security/server/service/TokenService.java b/src/main/java/com/boot/security/server/service/TokenService.java new file mode 100644 index 0000000..059aedf --- /dev/null +++ b/src/main/java/com/boot/security/server/service/TokenService.java @@ -0,0 +1,20 @@ +package com.boot.security.server.service; + +import com.boot.security.server.dto.LoginUser; +import com.boot.security.server.dto.Token; + +/** + * Token管理器 + * + * @author 小威老师 + * + * 2017年10月14日 + */ +public interface TokenService { + + Token saveToken(LoginUser loginUser); + + LoginUser getLoginUser(String token); + + boolean deleteToken(String token); +} diff --git a/src/main/java/com/boot/security/server/service/impl/TokenServiceImpl.java b/src/main/java/com/boot/security/server/service/impl/TokenServiceImpl.java new file mode 100644 index 0000000..72fc381 --- /dev/null +++ b/src/main/java/com/boot/security/server/service/impl/TokenServiceImpl.java @@ -0,0 +1,54 @@ +package com.boot.security.server.service.impl; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import com.boot.security.server.dto.LoginUser; +import com.boot.security.server.dto.Token; +import com.boot.security.server.service.TokenService; + +@Service +public class TokenServiceImpl implements TokenService { + + /** + * token过期秒数 + */ + @Value("${token.expire.seconds}") + private Integer expireSeconds; + @Autowired + private RedisTemplate redisTemplate; + + @Override + public Token saveToken(LoginUser loginUser) { + String token = UUID.randomUUID().toString(); + redisTemplate.boundValueOps(getKey(token)).set(loginUser, expireSeconds, TimeUnit.SECONDS); + + return Token.builder().token(token).build(); + } + + @Override + public LoginUser getLoginUser(String token) { + return redisTemplate.boundValueOps(getKey(token)).get(); + } + + @Override + public boolean deleteToken(String token) { + if (redisTemplate.hasKey(getKey(token))) { + redisTemplate.delete(getKey(token)); + + return true; + } + + return false; + } + + private String getKey(String token) { + return "tokens:" + token; + } + +} diff --git a/src/main/java/com/boot/security/server/service/impl/UserDetailsServiceImpl.java b/src/main/java/com/boot/security/server/service/impl/UserDetailsServiceImpl.java index a8f6c3b..a330b6b 100644 --- a/src/main/java/com/boot/security/server/service/impl/UserDetailsServiceImpl.java +++ b/src/main/java/com/boot/security/server/service/impl/UserDetailsServiceImpl.java @@ -4,13 +4,13 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.LockedException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -18,6 +18,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import com.boot.security.server.dao.PermissionDao; +import com.boot.security.server.dto.LoginUser; import com.boot.security.server.model.Permission; import com.boot.security.server.model.SysUser; import com.boot.security.server.model.SysUser.Status; @@ -42,15 +43,20 @@ public class UserDetailsServiceImpl implements UserDetailsService { throw new DisabledException("用户已作废"); } - Set authorities = new HashSet<>(); + // 查询权限 List permissionList = permissionDao.listByUserId(sysUser.getId()); + + Set authorities = new HashSet<>(); permissionList.parallelStream().filter(p -> !StringUtils.isEmpty(p.getPermission())).forEach(p -> { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(p.getPermission()); authorities.add(grantedAuthority); }); - User user = new User(username, sysUser.getPassword(), authorities); - return user; + LoginUser loginUser = new LoginUser(); + BeanUtils.copyProperties(sysUser, loginUser); + loginUser.setAuthorities(authorities); + + return loginUser; } } diff --git a/src/main/java/com/boot/security/server/utils/ResponseUtil.java b/src/main/java/com/boot/security/server/utils/ResponseUtil.java new file mode 100644 index 0000000..130c3ce --- /dev/null +++ b/src/main/java/com/boot/security/server/utils/ResponseUtil.java @@ -0,0 +1,23 @@ +package com.boot.security.server.utils; + +import java.io.IOException; + +import javax.servlet.http.HttpServletResponse; + +import com.alibaba.fastjson.JSONObject; + +public class ResponseUtil { + + public static void responseJson(HttpServletResponse response, int status, Object data) { + try { + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "*"); + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(status); + + response.getWriter().write(JSONObject.toJSONString(data)); + } catch (IOException e) { + e.printStackTrace(); + } + } +}