Spring boot+Spring Security 4配置整合实例
Spring boot+Spring Security 4配置整合实例
本例所覆盖的内容:
1. 使用Spring Security管理用户身份认证、登录退出
2. 用户密码加密及验证
3. 采用数据库的方式实现Spring Security的remember-me功能
4. 获取登录用户信息。
本例所使用的框架:
1. Spring boot
2. Spring MVC
3. Spring Security
4. Spring Data JPA
5. thymeleaf
说明:
1. 本文针对采用Spring boot微框架之用户,完全采用Java config,不讨论xml配置。
2. 本例代码是完整的,请勿随意删减,否则不能运行。
一、 整合Spring Security
在pom.xml中加入如下片段:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
二、 配置Spring Security
几乎所有配置都在下面这个文件中完成:
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService customUserDetailsService;//code1
@Autowired @Qualifier("dataSource1")
private DataSource dataSource1; //code2
@Override
protected void configure(HttpSecurity http) throws Exception {
//允许所有用户访问”/”和”/home”
http.authorizeRequests().antMatchers("/", "/home").permitAll()
//其他地址的访问均需验证权限
.anyRequest().authenticated()
.and()
.formLogin()
//指定登录页是”/login”
.loginPage("/login")
.permitAll()
//登录成功后可使用loginSuccessHandler()存储用户信息,可选。
.successHandler(loginSuccessHandler())//code3
.and()
.logout()
//退出登录后的默认网址是”/home”
.logoutSuccessUrl("/home")
.permitAll()
.invalidateHttpSession(true)
.and()
//登录后记住用户,下次自动登录
//数据库中必须存在名为persistent_logins的表
//建表语句见code15
.rememberMe()
.tokenValiditySeconds(1209600)
//指定记住登录信息所使用的数据源
.tokenRepository(tokenRepository());//code4
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//指定密码加密所使用的加密器为passwordEncoder()
//需要将密码加密后写入数据库 //code13
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());//code5
//不删除凭据,以便记住用户
auth.eraseCredentials(false);
}
// Code5----------------------------------------------
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(4);
}
// Code4----------------------------------------------
@Bean
public JdbcTokenRepositoryImpl tokenRepository(){
JdbcTokenRepositoryImpl j=new JdbcTokenRepositoryImpl();
j.setDataSource(dataSource1);
return j;
}
// Code3----------------------------------------------
@Bean
public LoginSuccessHandler loginSuccessHandler(){
return new LoginSuccessHandler();//code6
}
}
code1----------------------------------------------
@Component
public class CustomUserDetailsService implements UserDetailsService {
@Autowired //数据库服务类
private SUserService suserService;//code7
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
//SUser对应数据库中的用户表,是最终存储用户和密码的表,可自定义
//本例使用SUser中的email作为用户名:
SUser user = suserService.findUserByEmail(userName); //code8
if (user == null) {
throw new UsernameNotFoundException("UserName " + userName + " not found");
}
// SecurityUser实现UserDetails并将SUser的Email映射为username
return new SecurityUser(user); //code9
}
}
Code2----------------------------------------------
@Configuration
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
public class MyConfiguration {
@Bean
public DataSource dataSource1() {
org.springframework.jdbc.datasource.DriverManagerDataSource ds = new org.springframework.jdbc.datasource.DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test");
ds.setUsername("root");
ds.setPassword("****");
return ds;
}
}
Code6----------------------------------------------
//可以在这里将用户登录信息存入数据库。
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler{
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException,
ServletException {
//获得授权后可得到用户信息 可使用SUserService进行数据库操作
SUser userDetails = (SUser)authentication.getPrincipal();
//输出登录提示信息
System.out.println("管理员 " + userDetails.getEmail() + " 登录");
System.out.println("IP :"+getIpAddress(request));
super.onAuthenticationSuccess(request, response, authentication);
}
public String getIpAddress(HttpServletRequest request){
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
Code7----------------------------------------------
@Service("suserService")
public class SUserService {
@Autowired
private SUserRepository suserRepository;//code10
public List<SUser> findAll() {
return suserRepository.findAll();
}
public SUser create(SUser user) {
return suserRepository.save(user);
}
public SUser findUserById(int id) {
return suserRepository.findOne(id);
}
public SUser login(String email, String password) {
return suserRepository.findByEmailAndPassword(email, password);
}
public SUser update(SUser user) {
return suserRepository.save(user);
}
public void deleteUser(int id) {
suserRepository.delete(id);
}
public SUser findUserByEmail(String email) {
return suserRepository.findUserByEmail(email);
}
}
Code8----------------------------------------------
@Entity
@Table(name = "s_user", catalog = "test")//code11
public class SUser implements java.io.Serializable {
private Integer id;
private String name;
private String email;
private String password;
private Date dob;
private Set<SRole> SRoles = new HashSet<SRole>(0);// Code12
public SUser() {
}
public SUser(String name, String email, String password, Date dob, Set<SRole> SRoles) {
this.name = name;
this.email = email;
this.password = password;
this.dob = dob;
this.SRoles = SRoles;
}
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(name = "name", length = 20)
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Column(name = "email", length = 20)
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
@Column(name = "password", length = 20)
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
@Temporal(TemporalType.DATE)
@Column(name = "dob", length = 10)
public Date getDob() {
return this.dob;
}
public void setDob(Date dob) {
this.dob = dob;
}
@OneToMany(fetch = FetchType.EAGER, mappedBy = "SUser")
public Set<SRole> getSRoles() {
return this.SRoles;
}
public void setSRoles(Set<SRole> SRoles) {
this.SRoles = SRoles;
}
}
Code9----------------------------------------------
public class SecurityUser extends SUser implements UserDetails
{
private static final long serialVersionUID = 1L;
public SecurityUser(SUser suser) {
if(suser != null)
{
this.setId(suser.getId());
this.setName(suser.getName());
this.setEmail(suser.getEmail());
this.setPassword(suser.getPassword());
this.setDob(suser.getDob());
this.setSRoles(suser.getSRoles());
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
Set<SRole> userRoles = this.getSRoles();
if(userRoles != null)
{
for (SRole role : userRoles) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getName());
authorities.add(authority);
}
}
return authorities;
}
@Override
public String getPassword() {
return super.getPassword();
}
@Override
public String getUsername() {
return super.getEmail();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
Code10----------------------------------------------
public interface SUserRepository extends JpaRepository<SUser, Integer> {
@Query("select u from SUser u where u.email=?1 and u.password=?2")
SUser login(String email, String password);
SUser findByEmailAndPassword(String email, String password);
SUser findUserByEmail(String email);
}
Code11----------------------------------------------
SUser对应的表和角色表,一个都不能少
CREATE TABLE `s_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`email` varchar(20) DEFAULT NULL,
`password` varchar(60) DEFAULT NULL,
`dob` date DEFAULT NULL,
PRIMARY KEY (`id`)
)
CREATE TABLE `s_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`uid` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `userrole` (`uid`),
CONSTRAINT `userrole` FOREIGN KEY (`uid`) REFERENCES `s_user` (`id`)
)
Code12----------------------------------------------
@Entity
@Table(name = "s_role", catalog = "test")
public class SRole implements java.io.Serializable {
private Integer id;
private SUser SUser;
private String name;
public SRole() {
}
public SRole(SUser SUser) {
this.SUser = SUser;
}
public SRole(SUser SUser, String name) {
this.SUser = SUser;
this.name = name;
}
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "uid", nullable = false)
public SUser getSUser() {
return this.SUser;
}
public void setSUser(SUser SUser) {
this.SUser = SUser;
}
@Column(name = "name", length = 20)
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
Code13----------------------------------------------
@SpringBootApplication
public class Application {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
SpringApplication app=new SpringApplication(Application.class);
Appctx.ctx=app.run(args);//将密码加密 必须保证数据库s_user中有id为1的用户//code14
SUserService suserService = (SUserService) Appctx.ctx.getBean("suserService");
SUser su= suserService.findUserById(1);
BCryptPasswordEncoder bc=new BCryptPasswordEncoder(4);
su.setPassword(bc.encode("111111"));
suserService.update(su);
}
Code14----------------------------------------------
public class Appctx {
public static ApplicationContext ctx=null;
public static Object getObject(String string){
return ctx.getBean(string);
}
}
Code15----------------------------------------------
//请勿手工写入数据 供remember-me功能使用
CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
)
Code15----------------------------------------------
public interface SRoleRepository extends JpaRepository<SRole,Integer> {
}
三、 Html及controller
index.html:
-------------------------------------------------------------------
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>Insert title here</title>
</head>
<body>
Welcome to Spring technology page!
</body>
</html>
hello.html
-------------------------------------------------------------------
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</body>
</html>
home.html
-------------------------------------------------------------------
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
</body>
</html>
login.html
-------------------------------------------------------------------
本页中 username password remember-me三个控件请勿改名。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<div th:if="${#httpServletRequest.remoteUser != null}">
<p th:text="${#httpServletRequest.remoteUser}">
sample_user
</p>
</div>
<form th:action="@{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
<input type="checkbox" name="remember-me" value="true" th:checked="checked"/><p>Remember me</p>
</form>
</body>
</html>
Controller:
-------------------------------------------------------------------
@Controller
@RequestMapping("/")
public class GreetingController {
@RequestMapping("/home")
public String home() {
return "home";
}
@RequestMapping("/hello")
public String hello() {
SecurityContext ctx = SecurityContextHolder.getContext();
Authentication auth = ctx.getAuthentication();
if(auth.getPrincipal() instanceof UserDetails)
{
SUser user = (SUser)auth.getPrincipal();
System.out.println(user.getEmail());
}
//本段代码演示如何获取登录的用户资料
return "hello";
}
@RequestMapping("/")
public String root() {
//如不进行此项配置,从login登录成功后,会提示找不网页
return "index";
}
}
四、 构建及运行顺序
1. 首先建立s_user s_role表,并向表中写入数据
2. 建立SUserService SUserRepository SUser SRoleRepository SRole类
3. 运行程序,将s_user中用户的密码加密
4. 如三中所述,配置Spring Security
5. 运行,访问http://localhost:8080/hello,系统出现如下界面:
用户名输入useremail 密码输入111111,点sign in则进入hello.html
重启浏览器,再访问http://localhost:8080/hello,则无需登录,直接到达。
在hello页面点sign out后,则返回home页面,退出了登录。