前言
- 我在毕设时为了方便设备与用户使用同一套后端而做的设置。
- Springboot在分离前后端时可以直接使用JSON传输。
- 如果有做权限需求,其实这个最重要的就是Spring Security JSON化问题,其他问题差不多就只是增删查改。
- 这个security JSON化在网页上一搜就很多,但是可以直接拿来使用是真的没有。SO我就直接把我的代码贴出来啦。
代码
前端
html
1 | <form class="layui-form changePwd"> |
JS
1 | form.on("submit(login)",function(data){ |
后端
security配置类——AppAuthenticationSuccessHandler
- 看名字就知道是什么类了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
@Component
public class AppAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler{
// Spring Security 通过RedirectStrategy对象负责所有重定向事务
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
/*
* 重写handle方法,方法中通过RedirectStrategy对象重定向到指定的url
* */
@Override
protected void handle(HttpServletRequest request, HttpServletResponse response,
Authentication authentication)
throws IOException {
// 通过determineTargetUrl方法返回需要跳转的url
String targetUrl = determineTargetUrl(authentication);
// 重定向请求到指定的url
redirectStrategy.sendRedirect(request, response, targetUrl);
}
/*
* 从Authentication对象中提取角色提取当前登录用户的角色,并根据其角色返回适当的URL。
*/
protected String determineTargetUrl(Authentication authentication) {
String url = "";
// 获取当前登录用户的角色权限集合
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
List<String> roles = new ArrayList<String>();
// 将角色名称添加到List集合
for (GrantedAuthority a : authorities) {
roles.add(a.getAuthority());
}
// 判断不同角色跳转到不同的url
if (isAdmin(roles)) {
url = "/page/admin/index.html";
} else if (isUser(roles)) {
url = "/index.html";
} else {
url = "/page/404.html";
}
System.out.println("url = " + url);
return url;
}
private boolean isUser(List<String> roles) {
if (roles.contains("ROLE_USER")) {
return true;
}
return false;
}
private boolean isAdmin(List<String> roles) {
if (roles.contains("ROLE_ADMIN")) {
return true;
}
return false;
}
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
protected RedirectStrategy getRedirectStrategy() {
return redirectStrategy;
}
}security配置类——AppSecurityConfigurer
- Security核心配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143import java.io.IOException;
import java.io.PrintWriter;
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.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import ink.mastermind.AllINOne.pojo.Json;
import ink.mastermind.AllINOne.service.UserService;
/**
* 自定义Spring Security认证处理类的时候
* 我们需要继承自WebSecurityConfigurerAdapter来完成,相关配置重写对应 方法即可。
* */
@Configuration
public class AppSecurityConfigurer extends WebSecurityConfigurerAdapter{
// 依赖注入用户服务类
@Autowired
private UserService userService;
// 依赖注入加密接口
@Autowired
private PasswordEncoder passwordEncoder;
// 依赖注入用户认证接口
@Autowired
private AuthenticationProvider authenticationProvider;
// 依赖注入认证处理成功类,验证用户成功后处理不同用户跳转到不同的页面
@Autowired
AppAuthenticationSuccessHandler appAuthenticationSuccessHandler;
/*
* BCryptPasswordEncoder是Spring Security提供的PasswordEncoder接口是实现类
* 用来创建密码的加密程序,避免明文存储密码到数据库
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// DaoAuthenticationProvider是Spring Security提供AuthenticationProvider的实现
@Bean
public AuthenticationProvider authenticationProvider() {
System.out.println("AuthenticationProvider authenticationProvider");
// 创建DaoAuthenticationProvider对象
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
// 不要隐藏"用户未找到"的异常
provider.setHideUserNotFoundExceptions(false);
// 通过重写configure方法添加自定义的认证方式。
provider.setUserDetailsService(userService);
// 设置密码加密程序认证
provider.setPasswordEncoder(passwordEncoder);
//new BCryptPasswordEncoder().encode(fkUser.getPassword().trim()))
return provider;
}
@Bean
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
Json json = new Json(200, "登录成功", null);
out.write(new ObjectMapper().writeValueAsString(json));
out.flush();
out.close();
}
});
filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
Json json = new Json(500, "登录失败", null);
out.write(new ObjectMapper().writeValueAsString(json));
out.flush();
out.close();
}
});
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("AppSecurityConfigurer configure auth......");
// 设置认证方式。
auth.authenticationProvider(authenticationProvider);
}
/**
* 设置了登录页面,而且登录页面任何人都可以访问,然后设置了登录失败地址,也设置了注销请求,注销请求也是任何人都可以访问的。
* permitAll表示该请求任何人都可以访问,.anyRequest().authenticated(),表示其他的请求都必须要有权限认证。
* */
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("AppSecurityConfigurer configure http......");
http.csrf().disable();
http.headers().frameOptions().sameOrigin();
http.authorizeRequests()
// spring-security 5.0 之后需要过滤静态资源
.antMatchers("/register.html","/register",
"/login.html","/login",
"/findPassword.html","/findPassword",
"/json/**","/layui/**","/images/**","/css/**","/js/**").permitAll()
.antMatchers("/page/**").hasAnyRole("USER","ADMIN")
.antMatchers("/admin/**").hasAnyRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").successHandler(appAuthenticationSuccessHandler)
.usernameParameter("userName").passwordParameter("password")
.and()
.logout().permitAll()
.and()
.exceptionHandling().accessDeniedPage("/login.html");
http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}security配置类——CustomAuthenticationFilter
- 这个是一个读取JSON的过滤器。它是继承UsernamePasswordAuthenticationFilter的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
ObjectMapper mapper = new ObjectMapper();
UsernamePasswordAuthenticationToken authRequest = null;
try (InputStream is = request.getInputStream()) {
Map<String, String> authenticationBean = mapper.readValue(is, Map.class);
authRequest = new UsernamePasswordAuthenticationToken(authenticationBean.get("userName"),
authenticationBean.get("password"));
} catch (IOException e) {
e.printStackTrace();
authRequest = new UsernamePasswordAuthenticationToken("", "");
} finally {
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
} else {
return super.attemptAuthentication(request, response);
}
}
}security配置类——MyPasswordEncoder
- 密码编码解码器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
public class MyPasswordEncoder implements PasswordEncoder{
public static MyPasswordEncoder m = new MyPasswordEncoder();
@Override
public String encode(CharSequence arg0) {
return new BCryptPasswordEncoder().encode(arg0.toString());
}
@Override
public boolean matches(CharSequence raw, String encode) {
return BCrypt.checkpw(raw.toString(), encode);
}
}用户服务类–UserService
- 这个类是为了调用数据库中用户,密码与权限所使用的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ink.mastermind.AllINOne.dao.UserDao;
import ink.mastermind.AllINOne.pojo.User;
import ink.mastermind.AllINOne.security.MyPasswordEncoder;
@Service("userService")
public class UserService implements UserDetailsService {
@Resource
private UserDao userDao;
/**
* @param user 注册用户
*/
@Transactional
public void register(User user) {
// TODO Auto-generated method stub
user.setPassword(MyPasswordEncoder.m.encode(user.getPassword()));
this.userDao.save(user);
}
/**
* @param user
* @return 通过用户名查找用户并返回
*/
@Transactional
public List<User> findUserByUserName(User user) {
// TODO Auto-generated method stub
return this.userDao.findByUserName(user.getUserName());
}
/**
* security模块
*/
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
// 调用持久层接口方法查找用户
List<User> l = this.userDao.findByUserName(userName);
User user = (l != null && !l.isEmpty()) ? l.get(0) : null;
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
// 创建List集合,用来保存用户权限,GrantedAuthority对象代表赋予给当前用户的权限
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(user.getRole()));
return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword().trim(),
authorities);
}
}JSON自制封装模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48public class Json {
private Integer status;
private String information;
private Object object;
public Json() {
super();
// TODO Auto-generated constructor stub
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getInformation() {
return information;
}
public void setInformation(String information) {
this.information = information;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
@Override
public String toString() {
return "Json [status=" + status + ", information=" + information + ", object=" + object + "]";
}
public Json(Integer status, String information, Object object) {
super();
this.status = status;
this.information = information;
this.object = object;
}
}链接
- 原来链接