1、构建工程
参考:前文 步骤一。
2、配置数据库和Security
application.properties内容如下:
#datasource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///springsecurity_demo?serverTimezone=UTC&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
#jpa
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
本工程根包为:com.zzz.rss
创建Security配置类:com.zzz.rss.config.SecurityConfig,内容如下:
package com.zzz.rss.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
//web应用安全适配器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
//告诉SpringSecurity 我们密码用什么加密
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//表单登录(身份认证)
http.formLogin()
.and()
//请求授权
.authorizeRequests()
//所有请求(包括/user)
.anyRequest()
//都需要我们身份认证
.authenticated();
}
}
3、三层和实体层的具体实现:
创建Controller层响应类com.zzz.rss.controller.UserController.java,代码如下:
package com.zzz.rss.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.annotation.JsonView;
import com.zzz.rss.domain.User;
//为Controller提供RestAPI
@RestController
@RequestMapping("/user") //简化@RequestMapping(value="/user",method=RequestMethod.GET)代码
public class UserController {
/**
* @Title: query
* @Description: 完成查询响应
* @param: @return 参数
* @return: List<User> 返回类型
* @throws
*/
//@RequestParam
//defaultValue 默认值
//name 传入参数的名字 ,对应上方法的参数,如果相同可以省略
//required 是否必须对应传入参数,默认true,没对应上报400错误
//value name别名相当于name
//@RequestMapping(value="/user",method=RequestMethod.GET)用@GetMapping代替,因为类的头部使用了@RequestMapping("/user")
@GetMapping
//@RequestParam username获取的是网址http://localhost:8080/user?username=xx的值,不是security的登录认证用户名
public List<User> query(@RequestParam String username) {
System.out.println(username);
List<User> list = new ArrayList<User>();
list.add(new User());
list.add(new User());
list.add(new User());
return list;
}
}
创建repository层(相当与dao层)操作数据库接口类com.zzz.rss.repository.UserRepository.java,内容如下:
package com.zzz.rss.repository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import com.zzz.rss.domain.User;
public interface UserRepository extends CrudRepository<User, Long> {
@Query(value="select * from user where username = ?1",nativeQuery=true)
User findUserByUsername(String username);
}
创建service层业务类com.zzz.rss.service.UserService.java实现登录认证功能,代码如下:
package com.zzz.rss.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import com.zzz.rss.domain.User;
import com.zzz.rss.repository.UserRepository;
/**
* @ClassName: UserService
* @Description: 用SpringSecurity默认的登录系统
* @author Administrator
* @date: 2022年3月28日 下午3:39:27
* @Copyright:
*/
@Component
public class UserService implements UserDetailsService{
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserRepository userRepository;
//spring security默认处理登录 username为输入的用户名
//访问任何页面被拦截,跳转login页面需要提供认证
//输入用户名和密码后,跳转到UserService
//根据用户名搜索到数据库对应的用户密码
//拿数据库密码来匹配认证,认证成功则返回domain的User对象表示认证成功,继续访问之前页面
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findUserByUsername(username);
if (user==null) {
throw new UsernameNotFoundException(username);
}
//用户,密码,权限
//domain User实现UserDetails接口,拿数据库密码来匹配认证
return new User(username, passwordEncoder.encode(user.getPassword()), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
最后创建domain实体类com.zzz.rss.domain.User.java,因为看起来比较复杂,所以放到最后。其实除了基本的用户属性和get/set方法以外,其他的都是继承Security的UserDetails接口类,参考org.springframework.security.core.userdetails.User实现类写的。
在eclipse菜单栏中找到navigate——》Open Type可搜索Security的User实现类的具体代码:
自己实现damain层User类的代码如下:
package com.zzz.rss.domain;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Transient;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
@Entity
public class User implements UserDetails{
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@Transient //不映射到数据库表
//用户权限
private Set<GrantedAuthority> authorities;
@Transient
//用户是否没失效
private boolean accountNonExpired;
@Transient
//用户是否没冻结
private boolean accountNonLocked;
@Transient
//用户是否没过期
private boolean credentialsNonExpired;
@Transient
//用户是否删除
private boolean enabled;
//给hibernate用的构造方法
public User() {
super();
}
public User(Long id, String username, String password) {
super();
this.id = id;
this.username = username;
this.password = password;
}
//给springsecurity用的构造方法
public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this(username, password, true, true, true, true, authorities);
}
public User(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
Assert.isTrue(username != null && !"".equals(username) && password != null,
"Cannot pass null or empty values to constructor");
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
}
private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {
Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
// Ensure array iteration order is predictable (as per
// UserDetails.getAuthorities() contract and SEC-717)
SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<>(new AuthorityComparator());
for (GrantedAuthority grantedAuthority : authorities) {
Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements");
sortedAuthorities.add(grantedAuthority);
}
return sortedAuthorities;
}
private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
@Override
public int compare(GrantedAuthority g1, GrantedAuthority g2) {
// Neither should ever be null as each entry is checked before adding it to
// the set. If the authority is null, it is a custom authority and should
// precede others.
if (g2.getAuthority() == null) {
return -1;
}
if (g1.getAuthority() == null) {
return 1;
}
return g1.getAuthority().compareTo(g2.getAuthority());
}
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
//用户权限
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return authorities;
}
//用户是否没失效
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return accountNonExpired;
}
//用户是否没冻结
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return accountNonLocked;
}
//用户是否没过期
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return credentialsNonExpired;
}
//用户是否删除
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return enabled;
}
}
4、运行测试
在根包com.zzz.rss右键run as——》java application。运行起来后,在浏览器中访问http://localhost:8080/user,需要登录认证,输入用户名和密码即可完成登录。(这里我的数据库已经提前建好,数据库名是springsecurity_demo,user表包含id,password,username三个基本字段,然后自己添加即可测试用户数据即可。)
5、扩展:完成自定义登录页面
创建login.html,一定要放在static下。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<form action="/loginPage" method="post">
用户名
<input type="text" name="username">
<br>
密码
<input type="password" name="password">
<br>
<input type="submit">
</form>
</body>
</html>
修改Security配置文件com.zzz.rss.config.SecurityConfig.java:
package com.zzz.rss.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
//web应用安全适配器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
//告诉SpringSecurity 我们密码用什么加密
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//表单登录(身份认证)
http.formLogin()
//自定义登录页面:指定html
.loginPage("/login.html")
//自定义登录页面:用springsecurity自带的过滤器响应loginPage请求
.loginProcessingUrl("/loginPage")
.and()
//请求授权
.authorizeRequests()
//自定义登录页面:允许认证通过
.antMatchers("/login.html").permitAll()
//所有请求(包括/user)
.anyRequest()
//都需要我们身份认证
.authenticated()
//自定义登录页面:跨站请求仿造的防护取消
.and().csrf().disable();
}
}
重新运行工程,登录认证就会用自定义界面。