写在前面:本文是一步一步教大家实现一个基于SpringSecurity+Mybatis+Mysql+vue的demo,基于此可以快速开发自己的应用。
本文的目标读者是有一定springboot、mybatis、mysql、vue经验的同学看的,并不适合完全的小白,也不适合高手!
内容比较长,如果不想看,也可以直接去下载源码:(SpringSecurityDemo1)
如果想看SpringSecurity其他笔记,请见:SpringSecurity学习笔记
一、后端开发
1.新建Springboot应用
在本地新建springboot应用,如果有需要,可参看我之前的博文:使用IDEA新建springboot项目
2.添加依赖
在项目的pom文件中添加如下依赖
<!-- 引用spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 引入thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 引入mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- 引入mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
3.配置application.properties
在application.properties中配置如下:
#后端端口
server.port=8088
#配置数据库url,其中 security01 是数据库名,需要改成自己的
spring.datasource.url=jdbc:mysql://localhost:3306/security01?characterEncoding=UTF-8&useUnicode=true&useSSL=true&serverTimezone=UTC
#配置数据库用户
spring.datasource.username= 改成你的数据库用户名
#数据库密码
spring.datasource.password= 改成你的数据库密码
#mysql驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.mapper-locations:classpath:mapping/*.xml
4.配置mapper
(1)在org.security.mapper新建接口:UserMapper
@Mapper
public interface UserMapper {
User loadUserByUsername(String username);
List<Role> getRoleByUid(Integer id);
}
(2)在ersources/mapping中新建xml:UserMapping.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.security.mapper.UserMapper">
<select id="loadUserByUsername" resultType="org.security.user.User">
SELECT
*
FROM `user`
WHERE
username=#{username}
</select>
<select id="getRoleByUid" resultType="org.security.user.Role">
select *
from role r,user_role ur
where r.id=ur.rid
</select>
</mapper>
5.配置数据库
(1)安装和配置mysql数据库,如有需要,请看:Mysql数据库安装和配置
(2)新建数据库表
DROP TABLE IF EXISTS security01.role;
CREATE TABLE security01.role
(
id INT NOT NULL auto_increment,
name VARCHAR (32),
nameZh VARCHAR (32),
PRIMARY KEY (id)
);
DROP TABLE IF EXISTS security01.`user`;
CREATE TABLE security01.`user`
(
id INT NOT NULL auto_increment,
username VARCHAR (32),
password VARCHAR (255),
enabled TINYINT,
accountNonExpired TINYINT,
accountNonLocked TINYINT,
credentialsNonExpired TINYINT,
PRIMARY KEY (id)
);
DROP TABLE IF EXISTS security01.user_role;
CREATE TABLE security01.user_role
(
id INT NOT NULL auto_increment,
uid INT,
rid INT,
PRIMARY KEY (id),
KEY uid (uid),
KEY rid (rid)
);
(3)插入测试数据
因为insert的SQL比较简单,我这里就不写SQL了,各位可以自行写
6.写Entity
(1)写User类,实现UserDetails接口
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private Boolean enabled;
private Boolean accountNonExpired;
private Boolean accountNonLocked;
private Boolean credentialsNonExpired;
private List<Role> roles=new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities=new ArrayList<>();
for (Role role:roles){
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// public Boolean getEnabled() {
// return enabled;
// }
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
// public Boolean getAccountNonExpired() {
// return accountNonExpired;
// }
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
public void setAccountNonExpired(Boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
// public Boolean getAccountNonLocked() {
// return accountNonLocked;
// }
public void setAccountNonLocked(Boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
// public Boolean getCredentialsNonExpired() {
// return credentialsNonExpired;
// }
public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
}
(2)写Role类
public class Role {
private Integer id;
private String name;
private String nameZh;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNameZh() {
return nameZh;
}
public void setNameZh(String nameZh) {
this.nameZh = nameZh;
}
}
7.重写MyUserDetailService
新建MyUserDetailsService,实现UserDetailsService接口:
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user=userMapper.loadUserByUsername(username);
if(user==null){
throw new UsernameNotFoundException("用户不存在");
}
user.setRoles(userMapper.getRoleByUid(user.getId()));
System.out.println("##############password:"+user.getPassword());
System.out.println("##############encode:"+passwordEncoder.encode(user.getPassword()));
user.setPassword(passwordEncoder.encode(user.getPassword()));
return user;
}
}
8.新建登录成功/失败的处理类
(1)新建成功后的处理MyAuthenticationSuccessHandler
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
Map<String,Object> resp=new HashMap<>();
resp.put("status",200);
resp.put("msg","登录成功");
// 生成一个uuid返回给前端,用以作为token
String token= UUID.randomUUID().toString().replace("-","");
resp.put("token",token);
ObjectMapper om=new ObjectMapper();
String s=om.writeValueAsString(resp);
response.getWriter().write(s);
}
}
(2)新建失败后的处理MyAuthenticationFailureHandler
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
Map<String,Object> resp=new HashMap<>();
resp.put("msg","登录失败!"+e.getMessage());
System.out.println("登录失败:::"+e.toString());
if(e instanceof BadCredentialsException){
resp.put("status",401);
resp.put("msg","用户名或密码不正确");
}else{
resp.put("status",500);
resp.put("msg","系统内部错误");
}
ObjectMapper om=new ObjectMapper();
String s=om.writeValueAsString(resp);
response.getWriter().write(s);
}
}
9.新建SecurityConfig
配置加密方式、登录方式,登录成功及失败后的处理
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyUserDetailsService myUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/mylogin.html")
.loginProcessingUrl("/doLogin")
// 登录成功后,返回json
.successHandler(new MyAuthenticationSuccessHandler())
//登录失败后,返回json
.failureHandler(new MyAuthenticationFailureHandler())
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and().cors()
.and().logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessHandler((req,resp,auth)->{
resp.setContentType("application/json;charset=utf-8");
Map<String,Object> result=new HashMap<>();
result.put("status",200);
result.put("msg","注销成功");
ObjectMapper om=new ObjectMapper();
String s=om.writeValueAsString(result);
resp.getWriter().write(s);
})
.and()
.csrf().disable();
}
}
二、前端配置
说明:我最近研究的是SpringSecurity,所以前端就拿之前自己的一个前端项目来改
1.修改后端base地址
修改main.js
Axios.defaults.baseURL = 'http://localhost:1025/'
说明:如果此时运行系统,会报跨域访问的错,其中一种方法是下面第三点,使用Nginx来配置代理。
三、Nginx配置代理
如果对Nginx不了解的同学,可参看我写的博文:Nginx实现跨域访问
1.修改nginx.conf配置
server{
listen 1025;
server_name localhost;
location / {
add_header 'Access-Control-Allow-Origin' http://localhost:8080;
add_header 'Access-Control-Allow-Credentials' true;
add_header 'Access-Control-Allow-Headers' *;
add_header 'Access-Control-Allow-Methods' *;
add_header 'Access-Control-Expose-Headers' *;
proxy_pass http://localhost:8088;
}
}