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 extends GrantedAuthority> 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();
+ }
+ }
+}