springboot springsecurity 基于html 通过 json 方式登录 + token 验证 + remember me

数据库

 

 

 pom.xml

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

</dependencies>

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserService userService;

@Autowired
private DataSource dataSource;

String [] urls = {"/**/*.html","/js/**","/logon"};

@Override
protected void configure(HttpSecurity http) throws Exception {


http
.authorizeRequests()
.antMatchers(urls)
.permitAll()
.anyRequest()
.authenticated()
.and()
.rememberMe() //添加后会走RememberMeAuthenticationFilter 在所有认证失败后去通过cookie 认证
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60 * 60 * 60) // 添加后会走RememberMeAuthenticationFilter 需要知道过期时间
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().securityContext() //不使用session
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
//添加Token处理的过滤器
http.addFilterAfter(tokenFilter(), UsernamePasswordAuthenticationFilter.class);
}


//rememer-me tomken持久化的bean
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl persistentTokenRepository = new JdbcTokenRepositoryImpl();
persistentTokenRepository.setDataSource(dataSource);
// persistentTokenRepository.setCreateTableOnStartup(true); 是否自动创建表
return persistentTokenRepository;
}

@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.inMemoryAuthentication().withUser("admin").password(NoOpPasswordEncoder.getInstance().encode("admin")).roles("USER");
//添加token 处理的认证
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder()).and().authenticationProvider(new TokenVerifyPovider());
}

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

@Bean
public TokenFilter tokenFilter() throws Exception {
return new TokenFilter(authenticationManagerBean(),urls);
}

//自定义在登录成功后手动将Remeber token 保存到数据库
@Bean
public RemeberMeFilter remeberMeFilter(){
return new RemeberMeFilter("remeber-me", userService, persistentTokenRepository(),60 * 60 * 60);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}

@Service
public class UserService implements UserDetailsService {

@Autowired
private UserDao userDao;

@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

User user = userDao.queryByUsername(s);

UserDetails userDetails = null;

if(Optional.ofNullable(user).isPresent()){
return new org.springframework.security.core.userdetails.User(s,user.getPassword(),true,true,true,true,user.getAuthorities());
}

return userDetails;
}
}


@Service
public class ValidateLoginInfoService {

@Autowired
AuthenticationManager authenticationManager;


@Autowired
RemeberMeFilter remeberMeFilter;

public boolean validateUser(String username, String password, String rememberMe,HttpServletRequest request, HttpServletResponse response){

UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username,password);

Authentication authentication = null;

try {
authentication = authenticationManager.authenticate(token);
} catch (AuthenticationException e) {
e.printStackTrace();
return false;
}

SecurityContextHolder.getContext().setAuthentication(authentication);
// rememberMeServices.loginSuccess(request,response,authentication);

     //登录验证成功后 手动执行将remember token保存到数据库
remeberMeFilter.loginSuccess2(request, response,authentication,rememberMe);


return true;
}
}

public class StatelessTokenAuthentication extends AbstractAuthenticationToken {

private HttpServletRequest request;
private HttpServletResponse response;

public StatelessTokenAuthentication (HttpServletRequest request, HttpServletResponse response){
super((Collection)null);
this.request = request;
this.response = response;
}

public StatelessTokenAuthentication(Object object){
super((Collection)null);
}

@Override
public Object getCredentials() {
return new Credentials();
}

@Override
public Object getPrincipal() {
return null;
}


class Credentials {
public HttpServletRequest getRequest(){
return StatelessTokenAuthentication.this.request;
}

public HttpServletResponse getResponse(){
return StatelessTokenAuthentication.this.response;
}
}
}


public class TokenVerifyPovider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//return new StatelessTokenAuthentication("success"); // 本例子中混合了token 认证和remember me认证,只要 token认证通过了 rememberme 认证就不会进行,所以只要返回null就可以进行 rememer认证,如果 只想token 认证成功返回注释的信息即可,需要返回什么自己扩展
return null;
}

@Override
public boolean supports(Class<?> authentication) {
return ClassUtils.isAssignable(StatelessTokenAuthentication.class, authentication);
}
}


@Entity
@Data
public class User implements UserDetails {

@Id
@GeneratedValue
private Integer id;
@Column(name = "name")
private String username;
private String password;
private String role;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> roleList = new ArrayList<GrantedAuthority>();
roleList.add(new SimpleGrantedAuthority("ROLE_" + this.role));
return roleList;
}

@Override
public boolean isAccountNonExpired() {
return false;
}

@Override
public boolean isAccountNonLocked() {
return false;
}

@Override
public boolean isCredentialsNonExpired() {
return false;
}

@Override
public boolean isEnabled() {
return false;
}

}
@RestController
public class LoginController {

@Autowired
private ValidateLoginInfoService validateLoginInfoService;

@Autowired
private HttpServletRequest request;

@Autowired
private HttpServletResponse response;

@PostMapping("logon")
public Object login(@RequestBody Map<String,String> map){
String username = map.get("username");
String password = map.get("password");
String rememberMe = map.get("remember-me");

boolean flag = validateLoginInfoService.validateUser(username,password,rememberMe,request,response);

Map<String,String> map3 = new HashMap<String,String>(){{
put("name",username);
}};

String token = JwtUtil.createToken(map3);

Map<String,String> map2 = new HashMap<>();
map2.put("status","success");
map2.put("token",token);

if(!flag){
map2.put("status","error");
}
return map2;
}

@PostMapping("main")
/// @PreAuthorize("hasRole('test')")
public Object main(){

Map<String,String> map2 = new HashMap<>();
map2.put("status","success");

return map2;
}
}

public interface UserDao extends JpaRepository<User, Integer> {

public User queryByUsername(String username);

@Query("select u from User u where u.username = ?1 and u.password = ?2")
public User queryByUsernameAndPassword(String name,String pwd);
}


//至于为什么继承PersistentTokenBasedRememberMeServices 请百度rememberme的原理,PersistentTokenBasedRememberMeServices 是最后持久化remember me token的类
public class RemeberMeFilter extends PersistentTokenBasedRememberMeServices {

public RemeberMeFilter(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {
super(key, userDetailsService, tokenRepository);
}

public RemeberMeFilter(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository,int tokenValiditySeconds) {
super(key, userDetailsService, tokenRepository);
this.setTokenValiditySeconds(tokenValiditySeconds);
}



public void loginSuccess2(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication,String rememberMe) {
if (rememberMe != null && Boolean.valueOf(rememberMe)) {
this.onLoginSuccess(request, response, successfulAuthentication);
}
}

}
//自定义过滤器不回忽略permitAll()的url, 这个查过原因好像是security issues ,需要自己处理这些url进行忽略
public class TokenFilter  extends OncePerRequestFilter {


private AuthenticationManager authenticationManager;

private String [] urls ;

public TokenFilter(AuthenticationManager authenticationManager, String[] urls) {
this.authenticationManager = authenticationManager;
this.urls = urls;
}

@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
FilterInvocation fi = new FilterInvocation(httpServletRequest,httpServletResponse,filterChain);
String requestUrl = fi.getRequestUrl();

List<RequestMatcher> list = new ArrayList<>();
Stream.of(this.urls).forEach(url -> list.add(new AntPathRequestMatcher(url)));

OrRequestMatcher requestMatcher = new OrRequestMatcher(list);
boolean flag = requestMatcher.matches(httpServletRequest);

if(flag || httpServletRequest.getMethod().equals(HttpMethod.GET.toString())) {

super.doFilter(httpServletRequest, httpServletResponse, filterChain);

}else{

try {
StatelessTokenAuthentication statelessTokenAuthentication = new StatelessTokenAuthentication(httpServletRequest,httpServletResponse);
Authentication authentication = authenticationManager.authenticate(statelessTokenAuthentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (AuthenticationException e) {
e.printStackTrace();
}

super.doFilter(httpServletRequest, httpServletResponse, filterChain);
}

}
}

public class JwtUtil {
private final static String secret = "abcdefg";
// iss: jwt签发者
// sub: jwt所面向的用户
// aud: 接收jwt的一方
// exp: jwt的过期时间,这个过期时间必须要大于签发时间
// nbf: 定义在什么时间之前,该jwt都是不可用的.
// iat: jwt的签发时间
// jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

public static String createToken(Map claims){
return Jwts.builder().setClaims(claims)
.setExpiration(DateUtils.plusDays(1))
.setId(Objects.toString(DateUtils.getSecond()))
.setIssuedAt(DateUtils.getCurrentDate()) //发布时间
.setSubject("authentication token")
.setIssuer("test")
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}

public static Claims parseToken(String token){
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
return claims;
}


}
}
public class DateUtils {

public static Date getCurrentDate(){
LocalDateTime dateTime = LocalDateTime.now();
return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
}


public static Date plusDays(int day){
LocalDateTime dateTime = LocalDateTime.now();
dateTime = dateTime.plusDays(day);
return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
}

public static long getSecond(){
LocalDateTime dateTime = LocalDateTime.now();
return dateTime.getSecond();
}
}

application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=update

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">


<title>Title</title>
<script src="js/jquery-3.4.1.min.js"></script>
<script>
$(function(){

$("#sub").click(function(){
var username = $("#username").val();
var password = $("#password").val();
var remeberMe = $("#remember-me").val();
var token = document.cookie.split('; ').reduce(function(result, v, i, a) { var k = v.split('='); result[k[0]] = k[1]; return result; }, {});
$.ajax({
url: '/logon',
type:'post',
data:JSON.stringify({"username": username,"password": password,"remember-me":remeberMe}),
contentType: "application/json",
dataType: "json",
beforeSend: function(request) {
request.setRequestHeader("X-XSRF-TOKEN",token["XSRF-TOKEN"]);
},
success: function(response){
token2 = response["token"];
sessionStorage.setItem("token2",token2);
},
error: function(response){
alert(response)
}
})
})

$("#sub2").click(function(){

var token = document.cookie.split('; ').reduce(function(result, v, i, a) { var k = v.split('='); result[k[0]] = k[1]; return result; }, {});

$.ajax({
url: '/main',
type:'post',
beforeSend: function(request) {
request.setRequestHeader("X-XSRF-TOKEN",token["XSRF-TOKEN"]);
request.setRequestHeader("TOKEN",sessionStorage.getItem("token2"));
},
success: function(response){
alert(response)
},
error: function(response){
alert(response)
}
})
})
})
</script>
</head>
<body>
username: <input type="text" name="username" id="username"></br>
password: <input type="text" name="password" id="password"></br>
remeber-me: <input type="checkbox" name="remember-me" id="remember-me" value="true"></br>
<button style="width: 50px;height: 25px" id="sub">submit</button>

<button style="width: 50px;height: 25px" id="sub2">submit2</button>
</body>
</html>
posted @ 2020-05-24 16:14  tangfeifei2020  阅读(569)  评论(0)    收藏  举报